Skill v1.0.0
currentAutomated scan100/100version: "1.0.0" name: llm-assisted-curation description: Use locally-hosted LLMs (vLLM/SGLang) for dataset filtering, quality scoring, rewriting, labeling, and synthetic data generation. Covers LLM-as-judge scoring, structured output filtering, batch inference pipelines, and 2025-2026 techniques (DataRater, perplexity filtering, curriculum scoring, LLM-based dedup). tags: [llm-curation, vllm, sglang, data-quality, llm-as-judge, synthetic-data, dataset-filtering, dataset-curation, mlops]
LLM-Assisted Dataset Curation
Overview
Modern dataset curation uses LLMs as quality filters, rewriters, labelers, and synthetic data generators. This skill covers hosting models locally with vLLM/SGLang and using them for dataset work — not for interactive chat, but for batch, structured, reproducible data operations.
When to Use
Use this skill when:
- Scoring or filtering dataset examples with an LLM quality judge.
- Rewriting noisy text (queries, answers, reasoning traces) in bulk.
- Generating synthetic examples to balance classes or fill gaps.
- Extracting structured labels from unstructured text.
- Running curriculum scoring (difficulty, complexity, educational value).
- Implementing DataRater-style learned quality scoring (2025).
Do not use for:
- Interactive chat or single-example inspection — use a UI.
- Exact-match deduplication — use hashing.
- Embedding-based dedup — use
embedding-analysisskill.
Prerequisites
Requires a running vLLM or SGLang server. See vllm and sglang skills for server setup.
# vLLM (high throughput)vllm serve Qwen/Qwen2.5-7B-Instruct --port 8000 --max-model-len 8192# SGLang (structured output)python -m sglang.launch_server --model-path Qwen/Qwen2.5-7B-Instruct --port 30000
Core Patterns
1. LLM-as-Judge Quality Scoring
Score each example on clarity, correctness, and usefulness.
from openai import OpenAIimport jsonfrom datasets import load_datasetclient = 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, # deterministicmax_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 datasetsdataset = load_dataset("my-dataset", split="train")scored = dataset.map(score_example)# Filter low-quality examplesfiltered = scored.filter(lambda x: x["clarity"] >= 3 and x["correctness"] >= 3)
2. Structured Output Filtering (SGLang)
Use SGLang's constrained decoding for guaranteed JSON schema output.
import sglang as sgl@sgl.functiondef 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. Batch Rewriting/Refinement
Clean noisy data by rewriting through an 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.contentreturn sample# Process with concurrencyfrom concurrent.futures import ThreadPoolExecutor, as_completeddef batch_rewrite(dataset, client, model, max_workers=8):with ThreadPoolExecutor(max_workers=max_workers) as executor:futures = {executor.submit(rewrite_text, example, client, model): ifor 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. Synthetic Data Generation
Generate additional examples to fill class imbalances or cover edge cases.
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 usefulREAL 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 diversitymax_tokens=2048,)try:generated = json.loads(response.choices[0].message.content)synthetic.extend(generated)except json.JSONDecodeError:continuereturn synthetic
5. Curriculum Difficulty Scoring
Score examples by difficulty to enable curriculum learning.
DIFFICULTY_PROMPT = """Rate the difficulty of this example on a scale of 1-5:1 = Trivial, basic knowledge2 = Easy, common knowledge3 = Moderate, requires some reasoning4 = Hard, requires deep understanding5 = Expert, requires specialized knowledgeExample: {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 difficultyscored = dataset.map(lambda x: {"difficulty": score_difficulty(x, client, model)})curriculum = scored.sort("difficulty")
6. LLM-Based Label Extraction
Extract structured labels from unstructured text.
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": []}
Optimization Patterns
Openai Batch API (vLLM)
# vLLM supports batch API for cost efficiency on large jobs# Upload a JSONL file of requestsrequests = []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, jsonwith tempfile.NamedTemporaryFile(mode="w", suffix=".jsonl", delete=False) as f:for req in requests:f.write(json.dumps(req) + "\n")batch_file = f.namebatch = 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")
2025-2026 Literature Integration
This skill integrates techniques from:
| Paper | Venue | Technique | How Applied | |
|---|---|---|---|---|
| DataRater (Calian et al.) | NeurIPS 2025 | Meta-learned quality scoring | embedding_quality_score() in embedding-analysis; LLM judge as proxy | |
| Why Less is More (Dohmatob et al.) | 2025 | Theory of data curation thresholds | Informs filtering aggressiveness | |
| GRAPE Score | 2025 | Perplexity-based filtering | grape_score() in embedding-analysis | |
| NeMo Curator SemDedup | 2024-2025 | Clustering-based semantic dedup | semantic_dedup() in embedding-analysis | |
| LSHBloom (Khan et al.) | 2025 | Internet-scale text dedup | lsh_semantic_dedup() for >100M scale | |
| Blu-WERP (Rupesh et al.) | 2025 | Scalable preprocessing pipeline | Streaming + batched map pattern | |
| TBDFiltering (Busa-Fekete et al.) | 2025 | Tree-based data filtering | LLM scoring as tree node condition | |
| Ensembled Multimodal Curation (Xu et al.) | 2025 | Multi-signal quality fusion | Combine LLM scores + embedding scores + perplexity |
Quality Gate
An LLM-assisted curation run is complete when:
- The LLM server (vLLM/SGLang) is healthy and reachable.
- Scoring prompts are versioned and produce structured, parseable output.
- Filtered examples are saved with their scores for auditability.
- Synthetic data is flagged with a
synthetic: truefield. - Batch results are reproducible (temperature=0 for scoring, fixed seed for generation).
- A before/after dataset card documents what was filtered and why.