llm-assisted-curation

Par mkurman · zorai

Utilisez des LLM hébergés localement (vLLM/SGLang) pour le filtrage de datasets, la notation de qualité, la réécriture, l'étiquetage et la génération de données synthétiques. Couvre la notation LLM-as-judge, le filtrage par sorties structurées, les pipelines d'inférence par batch, ainsi que les techniques 2025-2026 (DataRater, filtrage par perplexité, curriculum scoring, déduplication par LLM).

npx skills add https://github.com/mkurman/zorai --skill llm-assisted-curation

Curation de Datasets Assistée par LLM

Vue d'ensemble

La curation moderne de datasets utilise les LLMs comme filtres de qualité, réécrits, étiqueteurs et générateurs de données synthétiques. Cette compétence couvre l'hébergement local de modèles avec vLLM/SGLang et leur utilisation pour des travaux de dataset — non pas pour du chat interactif, mais pour des opérations de données batch, structurées et reproductibles.

Quand utiliser

Utilisez cette compétence pour :

  • Scorer ou filtrer des exemples de dataset avec un juge de qualité LLM.
  • Réécrire en masse du texte bruyant (requêtes, réponses, traces de raisonnement).
  • Générer des exemples synthétiques pour équilibrer les classes ou combler les lacunes.
  • Extraire des étiquettes structurées à partir de texte non structuré.
  • Exécuter un scoring de curriculum (difficulté, complexité, valeur pédagogique).
  • Implémenter un scoring de qualité appris de style DataRater (2025).

Ne l'utilisez pas pour :

  • Le chat interactif ou l'inspection d'exemples uniques — utilisez une interface.
  • La déduplication par correspondance exacte — utilisez le hachage.
  • La déduplication basée sur embeddings — utilisez la compétence embedding-analysis.

Prérequis

Nécessite un serveur vLLM ou SGLang en cours d'exécution. Voir les compétences vllm et sglang pour la configuration du serveur.

# vLLM (débit élevé)
vllm serve Qwen/Qwen2.5-7B-Instruct --port 8000 --max-model-len 8192

# SGLang (sortie structurée)
python -m sglang.launch_server --model-path Qwen/Qwen2.5-7B-Instruct --port 30000

Modèles centraux

1. Scoring de qualité LLM-as-Judge

Scorerez chaque exemple sur la clarté, l'exactitude et l'utilité.

from openai import OpenAI
import json
from datasets import load_dataset

client = OpenAI(base_url="http://localhost:8000/v1", api_key="not-needed")

QUALITY_PROMPT = """Score the following example on these dimensions (1-5 each):
- clarity: Is the text well-written and understandable?
- correctness: Are the facts accurate?
- usefulness: Would this help someone learn or solve a problem?

Respond with ONLY valid JSON: {"clarity": N, "correctness": N, "usefulness": N}

Example:
{sample}
"""

def score_example(sample: dict) -> dict:
    prompt = QUALITY_PROMPT.format(sample=json.dumps(sample))
    response = client.chat.completions.create(
        model="Qwen/Qwen2.5-7B-Instruct",
        messages=[{"role": "user", "content": prompt}],
        temperature=0.0,  # deterministic
        max_tokens=128,
    )
    try:
        scores = json.loads(response.choices[0].message.content)
    except json.JSONDecodeError:
        scores = {"clarity": 0, "correctness": 0, "usefulness": 0}
    return {**sample, **scores}

# Batch scoring with datasets
dataset = load_dataset("my-dataset", split="train")
scored = dataset.map(score_example)

# Filter low-quality examples
filtered = scored.filter(lambda x: x["clarity"] >= 3 and x["correctness"] >= 3)

2. Filtrage de sortie structurée (SGLang)

Utilisez le décodage contraint de SGLang pour une sortie de schéma JSON garantie.

import sglang as sgl

@sgl.function
def classify_quality(s, text: str):
    s += sgl.system("You classify dataset examples. Output ONLY valid JSON.")
    s += sgl.user(f"Classify this example:\n\n{text}")
    s += sgl.gen("result", max_tokens=256, temperature=0.0, schema=json.dumps({
        "type": "object",
        "properties": {
            "quality": {"type": "string", "enum": ["high", "medium", "low", "noise"]},
            "language": {"type": "string", "enum": ["en", "code", "other"]},
            "topic": {"type": "string"},
            "issues": {"type": "array", "items": {"type": "string"}},
        },
        "required": ["quality", "language", "topic", "issues"],
    }))

state = classify_quality.run(text=example["text"])
result = state["result"]  # guaranteed valid JSON

3. Réécriture/Raffinement en batch

Nettoyez les données bruyantes en les réécrivant à travers un LLM.

REWRITE_PROMPT = """Rewrite the following text to be clear, grammatical, and well-structured.
Preserve all factual information. Fix typos, grammar, and awkward phrasing.

Original: {text}

Rewritten:"""

def rewrite_text(sample: dict, client, model: str) -> dict:
    prompt = REWRITE_PROMPT.format(text=sample["text"])
    response = client.chat.completions.create(
        model=model,
        messages=[{"role": "user", "content": prompt}],
        temperature=0.3,
        max_tokens=1024,
    )
    sample["text_rewritten"] = response.choices[0].message.content
    return sample

# Process with concurrency
from concurrent.futures import ThreadPoolExecutor, as_completed

def batch_rewrite(dataset, client, model, max_workers=8):
    with ThreadPoolExecutor(max_workers=max_workers) as executor:
        futures = {
            executor.submit(rewrite_text, example, client, model): i
            for i, example in enumerate(dataset)
        }
        results = [None] * len(dataset)
        for future in as_completed(futures):
            idx = futures[future]
            results[idx] = future.result()
    return results

4. Génération de données synthétiques

Générez des exemples supplémentaires pour combler les déséquilibres de classes ou couvrir des cas limites.

SYNTHETIC_PROMPT = """Given this REAL example, generate {n} NEW examples that are:
- Semantically different (new variations, not paraphrases)
- Same difficulty level
- Same format and style
- Realistic and useful

REAL example:
{seed}

Generate {n} new examples as a JSON array of objects with the same keys.
Output ONLY the JSON array."""

def generate_synthetic(seed_examples, client, model, n_per_seed=5):
    synthetic = []
    for seed in seed_examples:
        prompt = SYNTHETIC_PROMPT.format(n=n_per_seed, seed=json.dumps(seed))
        response = client.chat.completions.create(
            model=model,
            messages=[{"role": "user", "content": prompt}],
            temperature=0.8,  # higher for diversity
            max_tokens=2048,
        )
        try:
            generated = json.loads(response.choices[0].message.content)
            synthetic.extend(generated)
        except json.JSONDecodeError:
            continue
    return synthetic

5. Scoring de difficulté de curriculum

Scorerez les exemples par difficulté pour activer l'apprentissage par curriculum.

DIFFICULTY_PROMPT = """Rate the difficulty of this example on a scale of 1-5:
1 = Trivial, basic knowledge
2 = Easy, common knowledge
3 = Moderate, requires some reasoning
4 = Hard, requires deep understanding
5 = Expert, requires specialized knowledge

Example: {sample}

Difficulty (number only):"""

def score_difficulty(sample, client, model):
    response = client.chat.completions.create(
        model=model,
        messages=[{"role": "user", "content": DIFFICULTY_PROMPT.format(sample=sample["text"])}],
        temperature=0.0,
        max_tokens=4,
    )
    try:
        return int(response.choices[0].message.content.strip())
    except ValueError:
        return 3  # default moderate

# Build curriculum: sort by difficulty
scored = dataset.map(lambda x: {"difficulty": score_difficulty(x, client, model)})
curriculum = scored.sort("difficulty")

6. Extraction d'étiquettes basée sur LLM

Extrayez des étiquettes structurées à partir de texte non structuré.

LABELING_PROMPT = """Extract the following labels from this text.
Respond with ONLY valid JSON.

Text: {text}

Labels to extract:
- sentiment: "positive", "negative", or "neutral"
- has_code: true if contains code snippets, false otherwise
- domain: one of ["science", "technology", "business", "arts", "other"]
- entities: list of named entities mentioned
"""

def extract_labels(sample, client, model):
    response = client.chat.completions.create(
        model=model,
        messages=[{"role": "user", "content": LABELING_PROMPT.format(text=sample["text"])}],
        temperature=0.0,
        max_tokens=256,
    )
    try:
        labels = json.loads(response.choices[0].message.content)
        return {**sample, **labels}
    except json.JSONDecodeError:
        return {**sample, "sentiment": None, "has_code": None, "domain": None, "entities": []}

Modèles d'optimisation

Openai Batch API (vLLM)

# vLLM supports batch API for cost efficiency on large jobs
# Upload a JSONL file of requests
requests = []
for example in dataset:
    requests.append({
        "custom_id": str(example["id"]),
        "method": "POST",
        "url": "/v1/chat/completions",
        "body": {
            "model": "Qwen/Qwen2.5-7B-Instruct",
            "messages": [{"role": "user", "content": QUALITY_PROMPT.format(sample=example["text"])}],
            "temperature": 0.0,
            "max_tokens": 128,
        }
    })

import tempfile, json
with tempfile.NamedTemporaryFile(mode="w", suffix=".jsonl", delete=False) as f:
    for req in requests:
        f.write(json.dumps(req) + "\n")
    batch_file = f.name

batch = client.files.create(file=open(batch_file, "rb"), purpose="batch")
job = client.batches.create(input_file_id=batch.id, endpoint="/v1/chat/completions", completion_window="24h")

Intégration de la littérature 2025-2026

Cette compétence intègre des techniques de :

Article Lieu Technique Application
DataRater (Calian et al.) NeurIPS 2025 Scoring de qualité meta-appris embedding_quality_score() dans embedding-analysis; juge LLM comme proxy
Why Less is More (Dohmatob et al.) 2025 Théorie des seuils de curation de données Informe l'agressivité du filtrage
GRAPE Score 2025 Filtrage basé sur la perplexité grape_score() dans embedding-analysis
NeMo Curator SemDedup 2024-2025 Déduplication sémantique basée sur le clustering semantic_dedup() dans embedding-analysis
LSHBloom (Khan et al.) 2025 Déduplication de texte à l'échelle Internet lsh_semantic_dedup() pour >100M d'échelle
Blu-WERP (Rupesh et al.) 2025 Pipeline de prétraitement scalable Modèle streaming + map batch
TBDFiltering (Busa-Fekete et al.) 2025 Filtrage de données basé sur arbre Score LLM comme condition de nœud d'arbre
Ensembled Multimodal Curation (Xu et al.) 2025 Fusion de qualité multi-signaux Combinez scores LLM + scores embedding + perplexité

Porte de qualité

Une exécution de curation assistée par LLM est complète quand :

  • Le serveur LLM (vLLM/SGLang) est sain et accessible.
  • Les prompts de scoring sont versionnés et produisent une sortie structurée et analysable.
  • Les exemples filtrés sont sauvegardés avec leurs scores pour l'auditabilité.
  • Les données synthétiques sont signalées avec un champ synthetic: true.
  • Les résultats batch sont reproductibles (temperature=0 pour le scoring, graine fixe pour la génération).
  • Une fiche de dataset avant/après documente ce qui a été filtré et pourquoi.

Skills similaires