Skip to content
tnsaiDocs
GitHub

Evaluators

An evaluator scores one answer from 0 to 1, where higher is always better. Some are heuristics that run locally; the others ask a judge model. This page lists the built-in evaluators and the inputs each one needs.

All of them implement com.tnsai.evaluation.spi.Evaluator. To write your own, see Write an evaluator.

Set up a judge

Judge evaluators take an LLMJudge, a function from a prompt to the judge model's reply. Adapt any LLMClient with prompt -> llm.chat(prompt).getContent(), as the example below does.

A judge evaluator sends one prompt and parses a score from the reply. If the judge throws, the result fails with LLM judge error: <message>; if the reply has no score it can parse, the result fails and its details quote the start of the reply. Judge scores are only as good as the judge model, so use a capable one and spot-check its verdicts.

Call an evaluator directly

The benchmark runner does not pass test-case metadata to evaluators. Evaluators that read metadata, such as the RAG and multi-turn ones, are called directly with an EvaluationInput you build:

static List<EvaluationResult> score(LLMClient llm, String question, String answer,
                                    List<String> documents) {
    LLMJudge judge = prompt -> llm.chat(prompt).getContent();

    EvaluationInput input = EvaluationInput.builder()
        .userInput(question)
        .agentResponse(answer)
        .metadata("retrieved_documents", documents)
        .build();

    List<Evaluator> evaluators = List.of(
        new FaithfulnessEvaluator(judge),
        new AnswerRelevancyEvaluator(judge),
        new PIILeakageEvaluator(),
        new GEvalEvaluator("conciseness", "Is the answer short and free of filler?", judge));
    return evaluators.stream().map(evaluator -> evaluator.evaluate(input)).toList();
}

EvaluationInput.builder() takes userInput, agentResponse, expectedOutput, expectedToolSequence, actualToolSequence, instructions, latencyMs, costUsd, inputTokens, outputTokens and metadata(key, value). A result's score(), details() and metrics() explain the score; passed(threshold) compares it.

Heuristic evaluators

These run without a model.

ClassNameNeedsScores
ResponseQualityEvaluatorresponse_qualityagentResponse, expectedOutputWord overlap with the expected output: relevance, completeness and accuracy. Without an expected output, only the answer's length (100 characters scores 1)
ToolSelectionEvaluatortool_selectionexpectedToolSequence, actualToolSequencePrecision, recall and order of the tools used. No tools expected and none used scores 1; tools used when none were expected scores 0.5
InstructionFollowingEvaluatorinstruction_followinginstructions, agentResponseThe share of format and constraint checks it can detect in the instructions, such as JSON, bullet points or a word limit. No instructions scores 1
CostEfficiencyEvaluatorcost_efficiencycostUsd, latencyMs, inputTokens, outputTokensCost and latency against budgets (default $0.10 and 30 seconds) and the output-to-input token ratio. Missing values score 1
JsonCorrectnessEvaluatorjson_correctnessagentResponse; metadata belowValid JSON, required fields present and property types matching; an empty or invalid answer scores 0
PIILeakageEvaluatorpii_leakageagentResponse1 minus the share of enabled categories found: EMAIL, PHONE, CREDIT_CARD (Luhn-checked), TCKN, IBAN and IP_ADDRESS. Pass a Set<Category> to check fewer
StepEfficiencyEvaluatorstep_efficiencyactualToolSequence, and expectedToolSequence or metadata expected_stepsExpected steps divided by the larger of expected and actual. No expected count scores 1

Check JSON output

static EvaluationResult checkJson(String response) {
    EvaluationInput input = EvaluationInput.builder()
        .agentResponse(response)
        .metadata("required_fields", List.of("id", "status"))
        .metadata("json_schema", """
            {"properties": {"id": {"type": "integer"}, "status": {"type": "string"}}}
            """)
        .build();
    return new JsonCorrectnessEvaluator().evaluate(input);
}

The score is 0.4 for valid JSON, plus 0.3 times the share of required_fields present, plus 0.3 times the share of checked fields whose type matches. Answers that are not valid JSON score 0. json_schema is a JSON Schema string; only the type of each top-level property is checked, and properties missing from the answer are not counted. required_fields is a list of top-level field names.

Judge evaluators

Each takes an LLMJudge in its constructor.

ClassNameNeedsScores
FaithfulnessEvaluatorfaithfulnessagentResponse, metadata retrieved_documentsThe share of the answer's claims that the documents support
AnswerRelevancyEvaluatoranswer_relevancyuserInput, agentResponseDirectness, completeness and focus, each 1 to 5
ContextualPrecisionEvaluatorcontextual_precisionuserInput, metadata retrieved_documentsWhether the relevant documents are ranked first
ContextualRecallEvaluatorcontextual_recallexpectedOutput, metadata retrieved_documentsThe share of the expected answer's statements that the documents cover
HallucinationEvaluatorhallucinationagentResponse; metadata context or retrieved_documentsThe share of claims supported by the context. Without context, the judge looks for contradictions and invented references
BiasEvaluatorbiasagentResponse1 minus the judged bias on a 0 to 5 scale
ToxicityEvaluatortoxicityagentResponse1 minus the judged toxicity on a 0 to 5 scale
TaskCompletionEvaluatortask_completionagentResponse, and expectedOutput or instructions as the goalWhether the answer achieves the goal, 1 to 5
PlanAdherenceEvaluatorplan_adherenceinstructions holding the declared planWhether the run followed the plan, 1 to 5
ToolCorrectnessEvaluatortool_correctnessactualToolSequenceWhether each tool call was appropriate, 1 to 5. No tools used scores 1
ConversationCompletenessEvaluatorconversation_completenessmetadata conversation_history and conversation_goalWhether the conversation reached the goal, 1 to 5
KnowledgeRetentionEvaluatorknowledge_retentionmetadata conversation_history, at least two turnsThe share of earlier facts the assistant kept
TurnRelevancyEvaluatorturn_relevancymetadata conversation_history, at least two turns and one assistant turnContext alignment, query addressing and coherence of the last assistant turn

Scores on a 1 to 5 scale are normalized as (score − 1) / 4. retrieved_documents is a List<String>. conversation_history is a List<Map<String, String>> with role and content keys, oldest turn first.

Define your own criterion

GEvalEvaluator(criteriaName, description, judge) scores any criterion you describe in words. Its name is geval_<criteriaName>. It calls the judge twice: once to turn the description into evaluation steps, then once to score the answer from 1 to 5 against those steps. The metrics include raw_score and normalized_score. An empty answer fails without a judge call.