Classifiers

Feature set: Governance Contact our support for access.
This functionality evolves quickly, the behavior and APIs might change between releases without further notice.

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.

src/main/resources/application.conf
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:

src/main/resources/application.conf
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