Skip to main content

LLM Classification Validator

License: CC BY-NC-SA 4.0 Python 3.10+

The LLM classification validator is a five-dimension framework for testing LLM outputs.

Companion blog post: Validating LLM-Generated Control Mappings Beyond Aggregate Accuracy (Cloud Security Alliance)

For a detailed explanation of the methodology, see docs/METHODOLOGY.md.

Overview dashboard

The five dimensions

  1. Coherence — Do multiple runs (or multiple raters) agree? Inter-rater reliability via Cohen's kappa, Fleiss' kappa, and bootstrap confidence intervals.

  2. Consistency — Do outputs satisfy structural and semantic rules? Checks that labels exist in the target taxonomy, hierarchies are internally consistent, and required fields are present.

  3. Convergent validity — Does the LLM's mapping converge with an independently derived reference? Compares LLM labels against a transitive mapping through a third framework using kappa and Jaccard similarity.

  4. Adversarial discrimination — Can the LLM tell similar things apart? Minimal pairs that differ in one critical dimension, plus ambiguous inputs with multiple acceptable answers.

  5. Stability and sensitivity — Same input, same answer? Changed input, changed answer? Paraphrase invariance and perturbation sensitivity.

Each dimension produces a PASS / MARGINAL / FAIL verdict against configurable thresholds. An orchestrator consolidates all dimensions into a single evaluation report.

Installation

pip install -e .

# With development dependencies:
pip install -e ".[dev]"

Quick start

Coherence

from llm_classification_validator.coherence import run_coherence_analysis

raters = {
    "run_1": ["A", "B", "A", "C", "B"],
    "run_2": ["A", "B", "A", "C", "A"],
    "run_3": ["A", "B", "B", "C", "B"],
}
report = run_coherence_analysis(raters)
print(report.verdict)

Consistency

from llm_classification_validator.consistency import RuleRegistry, run_consistency_check
from llm_classification_validator.models import RuleResult

registry = RuleRegistry()

@registry.rule("R-001", "Label is non-empty", severity="error")
def label_present(item: dict) -> list[RuleResult]:
    passed = bool(item.get("label"))
    return [RuleResult(
        rule_id="R-001", rule_name="Label is non-empty",
        category="structural", severity="error",
        passed=passed, item_id=item.get("id"),
        message="OK" if passed else "Label missing",
    )]

items = [{"id": "1", "label": "A"}, {"id": "2", "label": ""}]
report = run_consistency_check(items, registry)
print(report.verdict)

Convergent validity

from llm_classification_validator.convergent import run_convergent_analysis

predicted = ["A", "B", "A", "C"]
reference = ["A", "B", "B", "C"]
report = run_convergent_analysis(predicted, reference)
print(report.verdict)

Adversarial discrimination

from llm_classification_validator.adversarial import MinimalPair, AmbiguityCase, run_adversarial_analysis

def my_classifier(text: str) -> str:
    return "category_A"

pairs = [MinimalPair("p1", "input alpha", "input beta", "A", "B")]
ambiguity = [AmbiguityCase("a1", "ambiguous input", ["A", "B"])]
report = run_adversarial_analysis(my_classifier, pairs, ambiguity)

Stability

from llm_classification_validator.stability import (
    ParaphraseVariant, PerturbationVariant, ExpectedDirection,
    run_stability_analysis,
)

base_items = {"item1": "original text", "item2": "other text"}

def classifier(text: str) -> dict[str, str]:
    return {"category": "A", "subcategory": "A.1"}

paraphrases = [
    ParaphraseVariant("item1", "item1_p1", "formal", "the original text, formally"),
]
report = run_stability_analysis(base_items, classifier, paraphrases=paraphrases)

Full evaluation

Pass dimension runners (zero-argument callables returning DimensionReport) to the orchestrator:

from llm_classification_validator.runner import run_evaluation

def my_coherence_runner():
    return run_coherence_analysis(...)

def my_consistency_runner():
    return run_consistency_check(...)

report = run_evaluation(
    foundation=[my_coherence_runner, my_consistency_runner],
    advanced=[my_adversarial_runner, my_stability_runner],
    parallel_advanced=True,
)
print(report.overall_verdict)
print(report.summary)

See examples/aicm_to_faircam.py for a complete working example using real CSA AI Controls Matrix controls. The AICM-to-FAIR-CAM mappings are illustrative — replace with your own taxonomy pair.

Configuration

All thresholds, bootstrap parameters, sampling settings, and runner options are configurable via YAML or Python.

Copy eval_config.yaml and override what you need:

coherence:
  thresholds:
    - metric: mean_kappa
      target: 0.80
      minimum: 0.60
  bootstrap:
    iterations: 5000
    confidence: 0.95

adversarial:
  discrimination_target: 0.90

sampling:
  min_per_stratum: 5
  min_total: 30
from llm_classification_validator.config import EvalConfig

config = EvalConfig.from_yaml("eval_config.yaml")

See eval_config.yaml for all available options with defaults.

Statistical methods

All computations use the standard library only and following statistical methods:

  • Cohen's kappa: two-rater categorical agreement
  • Fleiss' kappa: multi-rater categorical agreement
  • Jaccard similarity: set overlap
  • Bootstrap confidence intervals: percentile method, configurable iterations and seed
  • PASS / MARGINAL / FAIL verdicts: threshold-based with target and minimum levels

UI

The validation dashboard provides:

  • Dimension radar chart comparing actual scores against target and minimum thresholds
  • Configurable thresholds per dimension with live verdict recalculation
  • Per-control issue view aggregating problems across all dimensions
  • Adversarial detail panel showing minimal pair results
# Requires UI dependencies
pip install -e ".[ui]"

# Run the dashboard
PYTHONPATH=. python examples/run_ui.py

Adversarial detail panel

Tests

119 tests across 8 test files, all using standard library only.

pip install -e ".[dev]"
pytest

License

CC BY-NC-SA 4.0

Metadata

Release files for llm-classification-validator 1.0.2

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for llm-classification-validator 1.0.2
File Size Uploaded Provenance
llm_classification_validator-1.0.2.tar.gz 520.4 kB Without provenance Details

Built distribution (wheel)

Table of built distributions (wheels) for llm-classification-validator 1.0.2
File Interpreter ABI Platform
llm_classification_validator-1.0.2-py3-none-any.whl Python 3 none any Details

Total release size: 564.3 kB

Release files / llm_classification_validator-1.0.2.tar.gz

Download URL llm_classification_validator-1.0.2.tar.gz
Size 520.4 kB
Tags Source
SHA-256 checksum
How to use checksums
a7bc1a652277ba063a2a19d18e2ab426f140f0f26b2e1198fe09fb18134dd6e2
BLAKE2b-256 checksum
How to use checksums
be5ced6045a16fcf3a8e09eba4680ead1841e0b31f887a8c64a6424f18a5bdd0
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.13.13

Release files / llm_classification_validator-1.0.2-py3-none-any.whl

Download URL llm_classification_validator-1.0.2-py3-none-any.whl
Size 44.0 kB
Tags Python 3
SHA-256 checksum
How to use checksums
2edf6773faa557c582910f4f9cfa6c1efd8c25c43de6b29b7d0205274d79c19a
BLAKE2b-256 checksum
How to use checksums
d65538c8516c6dcdeb9fbf2a185252f45d5d774b360dbddda25e1aaf17633efb
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.13.13

Security for llm-classification-validator 1.0.2

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

No provenance found

None of the files in this release were uploaded with provenance metadata.

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page