ToneValidator

org.llm4s.agent.guardrails.builtin.ToneValidator
See theToneValidator companion object
class ToneValidator(allowedTones: Set[Tone]) extends OutputGuardrail

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:

  1. 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.
  2. Tone.Professional: contains please, thank you, kindly, regards or sincerely.
  3. Tone.Casual: contains hey, cool, awesome, yeah or nah.
  4. Tone.Friendly: contains hi, hello, thanks or appreciate.
  5. Tone.Formal: contains furthermore, moreover, consequently or therefore.
  6. 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.all accepts 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
Graph
Supertypes
trait Guardrail[String]
class Object
trait Matchable
class Any

Members list

Value members

Concrete methods

def validate(value: String): Result[String]

Detect the tone of value and check it is allowed.

Detect the tone of value and check it is allowed.

Value parameters

value

the output text to check; empty text is Neutral

Attributes

Returns

Right(value) unchanged when the detected tone is in the allowed set, otherwise Left with a org.llm4s.error.ValidationError for the field output naming the detected and the allowed tones

Inherited methods

def andThen(other: Guardrail[String]): Guardrail[String]

Compose this guardrail with another sequentially.

Compose this guardrail with another sequentially.

The second guardrail runs only if this one passes.

Value parameters

other

The guardrail to run after this one

Attributes

Returns

A composite guardrail that runs both in sequence

Inherited from:
Guardrail
def transform(output: String): String

Optional: Transform the output after validation. Default is identity (no transformation).

Optional: Transform the output after validation. Default is identity (no transformation).

Value parameters

output

The validated output

Attributes

Returns

The transformed output

Inherited from:
OutputGuardrail
def validate(value: String, context: ModelContext): Result[String]

Validates value inside an agent's hook, as validate(value) does, with the hook's context. A guardrail that calls a model overrides this to call through context.accounted, so the call is counted in the thread's usage and on the run's terminal event even when the task fails (design §4.22); one that combines guardrails passes context on to each. GuardrailMiddleware and GuardrailReviewMiddleware call this form.

Validates value inside an agent's hook, as validate(value) does, with the hook's context. A guardrail that calls a model overrides this to call through context.accounted, so the call is counted in the thread's usage and on the run's terminal event even when the task fails (design §4.22); one that combines guardrails passes context on to each. GuardrailMiddleware and GuardrailReviewMiddleware call this form.

Attributes

Inherited from:
Guardrail

Concrete fields

override val description: Option[String]

Optional description of what this guardrail validates.

Optional description of what this guardrail validates.

Attributes

val name: String

Name of this guardrail for logging and error messages.

Name of this guardrail for logging and error messages.

Attributes