Classifiers
Overview
A classifier maps an input string to a Classification: a numeric score, a categorical label, or both, with optional confidence and attributes.
You decide how it computes that result, for example with a regex, an embedding model, or a call to an external classification service.
A classifier separates scoring from acting on the score. A guardrail, an evaluator, a sanitizer, and an endpoint may all need to know how toxic a text is, but each does something different with the answer: a guardrail denies the call, an evaluator records a verdict, a sanitizer masks the text, an endpoint routes the request. Without classifiers, each of them embeds its own scoring logic. With classifiers, the scoring logic lives in one place, under one name, and the callers share it. Because the name comes from configuration, you can swap the implementation or tune its parameters at deployment time without changing the callers.
You enable a classifier in configuration and give it a name.
A guardrail, an evaluator, a sanitizer, or your own code then calls the classifier by that name through a ClassifierClient.
The runtime never invokes a classifier on its own. Unlike a guardrail, a classifier is not bound to a call boundary.
Implementing a classifier
A classifier implements the Classifier interface. classify maps the input string to a Classification:
import akka.javasdk.agent.Classification;
import akka.javasdk.agent.Classifier;
import akka.javasdk.agent.ClassifierContext;
public class ToxicityClassifier implements Classifier { (1)
private final double threshold;
public ToxicityClassifier(ClassifierContext context) { (2)
this.threshold = context.config().getDouble("threshold");
}
@Override
public Classification classify(String input) { (3)
double score = score(input); // a real implementation might call a model or an external API
String label = score >= threshold ? "toxic" : "clean";
return Classification.of(score, label); (4)
}
private double score(String input) {
return input.toLowerCase().contains("toxic") ? 1.0 : 0.0;
}
}
| 1 | Implement the Classifier interface. |
| 2 | An optional ClassifierContext constructor parameter gives access to the classifier’s configured name and its config section. |
| 3 | classify may block, for example on a model or external API call, because the runtime invokes classifiers on virtual threads. To compose futures instead, override classifyAsync. The default classifyAsync delegates to classify. |
| 4 | A Classification carries a score, a label, or both. Use the score, label, and of factory methods, or the full constructor to add confidence and attributes. |
The Classification record has four components: score, label, confidence (all Optional), and an attributes map. A classifier returns only what it computes.
A regex rule might return only a label, a scoring model only a score, and an ensemble a score plus per-member detail in the attributes.
Configuring classifiers
You enable classifiers under akka.javasdk.agent.classifiers.
Each entry is a named section whose only required property is the implementation class. The classifier reads any further properties from its ClassifierContext.
akka.javasdk.agent.classifiers {
"toxicity" { (1)
class = "com.example.classifier.ToxicityClassifier" (2)
threshold = 0.5 (3)
}
}
| 1 | The name the classifier is looked up by. This is the string passed to ClassifierClient.classify. |
| 2 | Implementation class. It must be public and have exactly one constructor, and that constructor must be public. |
| 3 | An application-specific property, read by the classifier from ClassifierContext.config(). |
A classifier configuration has no agents, agent-roles, or category: a classifier is not scoped to a boundary or a set of agents.
Callers invoke it by name where they need it.
The service constructs every configured classifier at startup. It fails to start if a class cannot be loaded, does not implement Classifier, or its constructor throws.
Invoking a classifier
A ClassifierClient can be injected into your components, such as a workflow or an endpoint, and into evaluators, sanitizers, and other classifiers.
A guardrail obtains one from its GuardrailContext.
The client resolves a configured classifier by name and invokes it:
Classification result = classifierClient.classify("toxicity", text); (1)
result
.label()
.ifPresent(label -> {
/* ... */
});
result
.score()
.ifPresent(score -> {
/* ... */
});
| 1 | classify blocks for the result. Component handlers run on virtual threads, so blocking is safe. Use classifyAsync for the non-blocking variant. Both throw IllegalArgumentException if no classifier is configured with that name. |
For a guardrail that consults a classifier to reach its decision, see Classifier-backed guardrails.
Composing classifiers
A ClassifierClient can also be injected into a classifier’s own constructor. One classifier can then resolve and combine others by name. The result is an ensemble built from configured members, where no member knows about the others:
import akka.javasdk.agent.Classification;
import akka.javasdk.agent.Classifier;
import akka.javasdk.agent.ClassifierClient;
import akka.javasdk.agent.ClassifierContext;
import java.util.List;
public class EnsembleClassifier implements Classifier {
private final ClassifierClient classifierClient; (1)
private final List<String> members;
public EnsembleClassifier(ClassifierContext context, ClassifierClient classifierClient) { (2)
this.classifierClient = classifierClient;
this.members = context.config().getStringList("members");
}
@Override
public Classification classify(String input) {
double worst = members
.stream()
.map(name -> classifierClient.classify(name, input)) (3)
.flatMap(c -> c.score().stream())
.max(Double::compare)
.orElse(0.0);
return Classification.score(worst); (4)
}
}
| 1 | Inject a ClassifierClient to call other configured classifiers. |
| 2 | The ClassifierContext supplies the ensemble’s own config, here the names of its members. |
| 3 | Resolve and invoke each member by name. |
| 4 | Combine the members' results into a single Classification. |
The corresponding configuration lists the members by name:
akka.javasdk.agent.classifiers {
"toxicity" {
class = "com.example.classifier.ToxicityClassifier"
threshold = 0.5
}
"profanity" {
class = "com.example.classifier.ProfanityClassifier"
}
"safety-ensemble" {
class = "com.example.classifier.EnsembleClassifier"
members = ["toxicity", "profanity"] (1)
}
}
| 1 | The ensemble reads its member names from config, so you can change the composition at deployment time without rebuilding. |
Testing
In a test, obtain a ClassifierClient from the testkit with getClassifierClient() and invoke your configured classifiers directly.
See Testing classifiers.
See also
-
Guardrails: checks that can consult a classifier to reach a decision.
-
Evaluators: evaluators can call classifiers as part of assessing an interaction.
-
Data sanitization: a sanitizer can mask what a classifier detects.
-
Governance and the runtime: where classifiers fit in Akka’s governance model.