Skip to content

Generate span-level safety supervision from Aegis 2.0 #44

Description

@adaamko

Problem

Aegis 2.0 labels safety at the example level — safe / unsafe plus a category — and has no span annotations. Training a span-level safety detector (issue #43) therefore requires generating span supervision, not converting existing annotations, because none exist at span granularity.

This is exactly how the hallucination benchmark was built, and the machinery is in the repo:

  • lettucedetect/generation/injection.py corrupts a known-good text into one with exact character-level spans by construction (see the pipeline overview in lettucedetect/generation/__init__.py:1-18).
  • lettucedetect/generation/classify.py uses an LLM to assign a type to an existing untyped span; scripts/classify_psiloqa_spans.py:1-20 is the end-to-end precedent for folding an external dataset with weaker labels into the span schema while preserving its splits.
  • The sample schema already carries everything needed: labels as {start, end, label, category, subcategory} dicts, plus dataset, task_type, and a free-form metadata dict for provenance (lettucedetect/datasets/hallucination_dataset.py:10-50).

Current behavior

No safety data exists in the repo in any form. What exists to reuse, with pointers:

  • Localization/typing of spans by LLM: lettucedetect/generation/classify.py, driven end-to-end by scripts/classify_psiloqa_spans.py.
  • Injection with exact offsets: lettucedetect/generation/injection.py.
  • Schema: lettucedetect/datasets/hallucination_dataset.py:10-50.
  • Precedent for a human-verified test slice: the code test split was expert-reviewed; the protocol is described in the model cards under docs/code-hallucination/.

What to do

  1. Localize within already-unsafe Aegis examples: an LLM annotator marks the minimal substring(s) that make the example unsafe, mapped to character offsets, keeping the Aegis category as the span's category. Follow the scripts/classify_psiloqa_spans.py structure (OpenAI-compatible endpoint via env vars, --limit for pilots, original splits preserved).
  2. Validate every produced offset programmatically: text[start:end] must equal the stored span text; drop or retry mismatches. This is the single most common failure mode of LLM span annotation.
  3. Inject unsafe spans into safe Aegis text to get balanced positives with exact offsets by construction, mirroring lettucedetect/generation/injection.py.
  4. Emit HallucinationSample-style records: the moderated message as answer, any dialogue history as context, dataset="aegis2", safety category in the span's category, source row id in metadata. Keep Aegis's own train/test split.
  5. Spot-check a random slice yourself; ideally produce a small human-verified test set (a few hundred rows), following the expert-review protocol used for the code test split.
  6. Hand off to issue Span-based safety classification (GliGuard / GuardReasoner-style) #43: train the span tagger on the generated data and report span char-F1 / example-level F1 via scripts/span_eval_metrics.py.

Check the Aegis 2.0 license and attribution requirements before redistributing any derived data.

Acceptance

This is a research issue: a pilot is a valid contribution. Concretely, any of:

  • a pilot slice (~500 localized + ~500 injected rows) with 100% offset integrity and a short note on annotator failure modes;
  • the full generation pipeline as a script under scripts/ (descriptive verb-phrase name, e.g. scripts/generate_aegis_safety_spans.py) with a docstring usage block;
  • a negative result: measured LLM-localization agreement too low to be usable, with numbers and examples.

For code that lands: offset-integrity and record-shaping functions are unit-tested (no network, canned LLM replies) in tests/test_aegis_spans_pytest.py (pattern required by tests/pytest.ini); python -m pytest tests/test_aegis_spans_pytest.py -v passes; python tests/run_pytest.py stays green.

Non-goals

Start here

git clone https://github.com/KRLabsOrg/LettuceDetect.git
cd LettuceDetect
pip install -e ".[dev]"
python tests/run_pytest.py   # should be green before you change anything

# The code to read first:
sed -n '1,25p' scripts/classify_psiloqa_spans.py       # the pipeline to mirror
sed -n '1,40p' lettucedetect/generation/__init__.py    # generation module overview
grep -n "class HallucinationSample" -A 40 lettucedetect/datasets/hallucination_dataset.py

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or requesthelp wantedExtra attention is neededresearchExploratory research; not a committed public API or release feature

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions