CoolFace
Apppublic

build-small-hackathon/hackathon-advisor

sourceHugging Facemitupdated 3mo agoView on Hugging Face
16likes
quest_dataset.py140 linesDownload Raw Back to hackathon_advisor
1"""Build the quest-classification SFT dataset.2 3Two responsibilities:4  1. Turn a crawled corpus record into the README / app-file segments that both the5     teacher labeller and the trained model see (front-loading imports and asset ids6     so the decisive evidence survives the prompt budget).7  2. Emit the chat-JSONL SFT file (manifest row + example rows) consumed by8     scripts/train_minicpm_lora.py and scripts/modal_train_quest_lora.py.9"""10from __future__ import annotations11 12import json13from typing import Any14 15from hackathon_advisor.quest_taxonomy import (16    QUEST_SYSTEM_PROMPT,17    QUESTS,18    build_app_segment,19    build_readme_segment,20    normalize_match,21    render_quest_prompt,22)23from hackathon_advisor._text import utc_now24 25 26LORA_DATASET_SCHEMA_VERSION = 127BASE_MODEL = "openbmb/MiniCPM5-1B"28ADAPTER_TASK = "hackathon_advisor_quest_classification"29 30 31def project_segments(record: dict[str, Any]) -> tuple[str, str]:32    return (33        build_readme_segment(record.get("readme_body", "")),34        build_app_segment(record.get("app_source", ""), record.get("app_signals", "")),35    )36 37 38def render_record_prompt(record: dict[str, Any], readme_segment: str, app_segment: str) -> str:39    return render_quest_prompt(40        title=record.get("title", ""),41        sdk=record.get("sdk", ""),42        declared_models=record.get("models", []),43        tags=record.get("tags", []),44        readme_segment=readme_segment,45        app_file_name=record.get("app_file", ""),46        app_file_segment=app_segment,47    )48 49 50def matches_to_completion(matches: list[dict[str, Any]]) -> str:51    """Render the gold completion exactly as the model must emit it (compact JSON)."""52    clean = [normalize_match(match) for match in matches]53    clean.sort(key=lambda match: match["confidence"], reverse=True)54    return json.dumps({"matches": clean}, ensure_ascii=False, separators=(",", ":"))55 56 57def build_example(prompt: str, matches: list[dict[str, Any]], *, meta: dict[str, Any]) -> dict[str, Any]:58    return {59        "type": "lora_sft_example",60        "schema_version": LORA_DATASET_SCHEMA_VERSION,61        "base_model": BASE_MODEL,62        "adapter_task": ADAPTER_TASK,63        "example_kind": meta.get("kind", "project"),64        "project_id": meta.get("project_id", ""),65        "variant": meta.get("variant", "natural"),66        "match_count": len(matches),67        "quests": sorted({match["quest"] for match in matches}),68        "messages": [69            {"role": "system", "content": QUEST_SYSTEM_PROMPT},70            {"role": "user", "content": prompt},71            {"role": "assistant", "content": matches_to_completion(matches)},72        ],73    }74 75 76def build_dataset_jsonl(examples: list[dict[str, Any]], *, source_note: str = "") -> str:77    quest_counts: dict[str, int] = {quest: 0 for quest in QUESTS}78    variant_counts: dict[str, int] = {}79    empty = 080    for example in examples:81        variant_counts[example["variant"]] = variant_counts.get(example["variant"], 0) + 182        if example["match_count"] == 0:83            empty += 184        for quest in example["quests"]:85            quest_counts[quest] = quest_counts.get(quest, 0) + 186    manifest = {87        "type": "lora_sft_manifest",88        "schema_version": LORA_DATASET_SCHEMA_VERSION,89        "generated_at": utc_now(),90        "app": "hackathon-advisor",91        "base_model": BASE_MODEL,92        "adapter_task": ADAPTER_TASK,93        "format": "chat-jsonl",94        "record_kinds": ["quest_classification"],95        "source": source_note or "build_small_hackathon_real_projects",96        "example_count": len(examples),97        "empty_match_examples": empty,98        "variant_counts": variant_counts,99        "quest_positive_counts": quest_counts,100        "quests": list(QUESTS),101    }102    records = [manifest, *examples]103    return "\n".join(json.dumps(record, ensure_ascii=False) for record in records) + "\n"104 105 106def parse_quest_dataset_jsonl(text: str) -> tuple[dict[str, Any], list[dict[str, Any]]]:107    records = [json.loads(line) for line in text.splitlines() if line.strip()]108    if not records:109        raise ValueError("quest dataset is empty")110    # Tolerate both layouts: a leading manifest row (local training file), or an111    # examples-only file (the Hub dataset, where the manifest lives in a sidecar so112    # the rows stay homogeneous for the dataset viewer). Synthesize a manifest when absent.113    if records[0].get("type") == "lora_sft_manifest":114        manifest, examples = records[0], records[1:]115    else:116        examples = records117        manifest = {118            "type": "lora_sft_manifest",119            "schema_version": LORA_DATASET_SCHEMA_VERSION,120            "base_model": BASE_MODEL,121            "adapter_task": ADAPTER_TASK,122            "format": "chat-jsonl",123            "example_count": len(examples),124        }125    for index, example in enumerate(examples, start=1):126        if example.get("type") != "lora_sft_example":127            raise ValueError(f"record {index} is not a lora_sft_example")128        messages = example.get("messages")129        if not isinstance(messages, list) or len(messages) < 2:130            raise ValueError(f"record {index} has no chat messages")131        assistant = messages[-1]132        if assistant.get("role") != "assistant" or not assistant.get("content"):133            raise ValueError(f"record {index} has no assistant completion")134        payload = json.loads(assistant["content"])135        if not isinstance(payload.get("matches"), list):136            raise ValueError(f"record {index} completion has no matches list")137        for match in payload["matches"]:138            normalize_match(match)139    return manifest, examples140