Install any skill in seconds. Free to start, no credit card required.
Get Started Free →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).
.claude/skills/mkurman-llm-assisted-curation/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-01 | ✗→✓ | ▲ Improved | 73% | 0% |
| case-11 | ✗→✓ | ▲ Improved | 155% | 0% |
| case-14 | ✗→✓ | ▲ Improved | 115% | 0% |
| case-18 | ✗→✓ | ▲ Improved | 139% | 0% |
| case-19 | ✗→✓ | ▲ Improved | 68% | 0% |
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.
Use this skill when:
Do not use for:
embedding-analysis skill.Requires a running vLLM or SGLang server. See vllm and sglang skills for server setup.
bash# 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
Score each example on clarity, correctness, and usefulness.
pythonfrom 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)
Use SGLang's constrained decoding for guaranteed JSON schema output.
pythonimport 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
Clean noisy data by rewriting through an LLM.
pythonREWRITE_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
Generate additional examples to fill class imbalances or cover edge cases.
pythonSYNTHETIC_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
Score examples by difficulty to enable curriculum learning.
pythonDIFFICULTY_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")
Extract structured labels from unstructured text.
pythonLABELING_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": []}
python# 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")
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 |
An LLM-assisted curation run is complete when:
synthetic: true field.| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-08 | pass→pass | 13,211 | 12,471 | -6% | 1 | 1 | 0% | 2,419 | 5,158 | +113% | 0 | 0 | — |
case-03 | pass→pass | 19,451 | 15,166 | -22% | 1 | 1 | 0% | 3,164 | 5,659 | +79% | 0 | 0 | — |
case-01 | fail→pass | 13,501 | 7,863 | -42% | 1 | 1 | 0% | 2,407 | 4,160 | +73% | 0 | 0 | — |
case-02 | pass→pass | 15,769 | 10,456 | -34% | 1 | 1 | 0% | 2,580 | 4,925 | +91% | 0 | 0 | — |
case-04 | pass→pass | 8,696 | 3,495 | -60% | 1 | 1 | 0% | 1,525 | 3,472 | +128% | 0 | 0 | — |
case-05 | pass→pass | 13,754 | 7,446 | -46% | 1 | 1 | 0% | 2,433 | 4,196 | +72% | 0 | 0 | — |
case-06 | pass→pass | 10,291 | 6,544 | -36% | 1 | 1 | 0% | 1,818 | 4,182 | +130% | 0 | 0 | — |
case-07 | fail→fail | 18,764 | 14,233 | -24% | 1 | 1 | 0% | 3,163 | 5,596 | +77% | 0 | 0 | — |
case-09 | pass→pass | 9,981 | 5,661 | -43% | 1 | 1 | 0% | 1,732 | 3,776 | +118% | 0 | 0 | — |
case-10 | pass→pass | 14,245 | 3,124 | -78% | 1 | 1 | 0% | 1,735 | 3,356 | +93% | 0 | 0 | — |
case-11 | fail→pass | 10,436 | 9,366 | -10% | 1 | 1 | 0% | 1,734 | 4,427 | +155% | 0 | 0 | — |
case-12 | pass→pass | 14,838 | 10,460 | -30% | 1 | 1 | 0% | 2,310 | 4,624 | +100% | 0 | 0 | — |
case-13 | pass→pass | 13,290 | 7,131 | -46% | 1 | 1 | 0% | 2,076 | 4,074 | +96% | 0 | 0 | — |
case-14 | fail→pass | 14,889 | 14,379 | -3% | 1 | 1 | 0% | 2,617 | 5,631 | +115% | 0 | 0 | — |
case-15 | pass→pass | 5,620 | 3,139 | -44% | 1 | 1 | 0% | 886 | 3,395 | +283% | 0 | 0 | — |
case-16 | pass→pass | 9,528 | 3,660 | -62% | 1 | 1 | 0% | 1,241 | 3,433 | +177% | 0 | 0 | — |
case-17 | pass→pass | 7,155 | 3,431 | -52% | 1 | 1 | 0% | 1,120 | 3,425 | +206% | 0 | 0 | — |
case-18 | fail→pass | 11,056 | 8,018 | -27% | 1 | 1 | 0% | 1,802 | 4,301 | +139% | 0 | 0 | — |
case-19 | fail→pass | 14,410 | 7,969 | -45% | 1 | 1 | 0% | 2,520 | 4,246 | +68% | 0 | 0 | — |
case-20 | pass→pass | 6,314 | 2,734 | -57% | 1 | 1 | 0% | 1,092 | 3,309 | +203% | 0 | 0 | — |
case-21 | pass→pass | 22,504 | 21,738 | -3% | 1 | 1 | 0% | 3,463 | 6,877 | +99% | 0 | 0 | — |
case-22 | pass→pass | 15,441 | 12,295 | -20% | 1 | 1 | 0% | 2,445 | 5,193 | +112% | 0 | 0 | — |
DecimalAI ran this skill against gemini-3.6-flash twice over the same eval suite — once with the skill loaded and once without — and compared the two runs case by case. 22 cases were attempted. The headline lift of +23 percentage points is the difference between those two pass rates over the 22 comparable cases.
Without the skill loaded, the model failed this case. With it loaded, the same prompt on the same model passed. This is one improved case from the latest verified run; every case, including any that regressed, is in the table above.
Other measured skills in the registry, with their headline benchmark lift.