Spaces:
Running
Running
| import html | |
| import re | |
| from typing import Any, Dict, List | |
| _HTML_TAG_RE = re.compile(r"<[^>]+>") | |
| _ESCAPED_TAG_RE = re.compile(r"\\<[^>]*\\>") # backslash-escaped: \</ref\> | |
| _SELF_CLOSING_RE = re.compile(r"/>") # stray self-closing fragments | |
| _CASCADE_RE = re.compile(r"[}\]\"]{3,}") # }}}}, ]]]], """ cascades | |
| _BRACKET_RE = re.compile(r"[\{\}\[\]]") | |
| _PUNCT_RE = re.compile(r"[|\\/]+") | |
| _WHITESPACE_RE = re.compile(r"\s+") | |
| def sanitize_text(text: str) -> str: | |
| """Remove HTML, layout, and bracket-cascade artifacts from training text. | |
| Handles: | |
| - Standard HTML tags: <tag>, </tag> | |
| - Backslash-escaped angle brackets from Docling: \\</ref\\>, \\</span\\> | |
| - Unicode-encoded angle brackets: \\u003c / \\u003e | |
| - Stray self-closing fragments: /> | |
| - HTML entities: > < & etc. | |
| - Cascading closing-bracket artifacts: }}}}, ]]]] | |
| """ | |
| if not text: | |
| return "" | |
| # 1. Decode unicode-escaped angle brackets (\u003c β <, \u003e β >) | |
| # These appear in post-Docling JSON that has been serialised with | |
| # ensure_ascii=True and then re-read as a Python string. | |
| cleaned = text.replace("\\u003c", "<").replace("\\u003e", ">") | |
| cleaned = cleaned.replace("\u003c", "<").replace("\u003e", ">") | |
| # 2. Strip backslash-escaped tags before the HTML regex sees them | |
| cleaned = _ESCAPED_TAG_RE.sub(" ", cleaned) | |
| # 3. Unescape remaining HTML entities (&   etc.) so that | |
| # the HTML-tag regex can then cleanly remove any residual tags. | |
| cleaned = html.unescape(cleaned) | |
| # 4. Remove all remaining HTML tags | |
| cleaned = _HTML_TAG_RE.sub(" ", cleaned) | |
| # 5. Remove stray self-closing fragment "/>", leftover bare ">" / "<" | |
| cleaned = _SELF_CLOSING_RE.sub(" ", cleaned) | |
| cleaned = cleaned.replace(">", " ").replace("<", " ") | |
| # 6. Remove cascading closing-bracket artifacts (}}}}}, ]]]]]) | |
| cleaned = _CASCADE_RE.sub(" ", cleaned) | |
| # 7. Remove remaining individual bracket noise | |
| cleaned = _BRACKET_RE.sub(" ", cleaned) | |
| # 8. Remove pipe / backslash / forward-slash noise | |
| cleaned = _PUNCT_RE.sub(" ", cleaned) | |
| # 9. Collapse all whitespace | |
| cleaned = _WHITESPACE_RE.sub(" ", cleaned) | |
| return cleaned.strip() | |
| def sanitize_example(example: Dict[str, Any]) -> Dict[str, Any]: | |
| """Sanitize instruction, input, and output fields in a training example.""" | |
| sanitized = {} | |
| for key in ("instruction", "input", "output"): | |
| value = example.get(key, "") | |
| if isinstance(value, str): | |
| sanitized[key] = sanitize_text(value) | |
| else: | |
| sanitized[key] = value | |
| return sanitized | |
| _THINK_BLOCK_RE = re.compile(r"<think>(.*?)</think>", re.DOTALL) | |
| _STEP_LABEL_RE = re.compile(r"\bStep\s+\d+", re.IGNORECASE) | |
| def _thinking_quality_check(output_text: str) -> bool: | |
| """Return True when a CoT output passes minimum quality standards. | |
| A reasoning example is rejected when any of the following hold: | |
| - No <think>...</think> block is present. | |
| - The think block body is fewer than 30 words (too terse to be useful). | |
| - The think block has fewer than 3 labelled step markers ("Step N"). | |
| - The final JSON answer appears verbatim inside the think block (answer | |
| leakage β the model would learn to write the answer before reasoning). | |
| Non-reasoning outputs (those without a <think> block at all but also without | |
| the 'think' instruction) are passed through unchanged; this check only fires | |
| when a <think> block is present. | |
| """ | |
| think_match = _THINK_BLOCK_RE.search(output_text) | |
| if think_match is None: | |
| # Not a CoT example β no think block expected, let it through. | |
| return True | |
| think_body = think_match.group(1).strip() | |
| # Minimum word count inside the think block. | |
| if len(think_body.split()) < 30: | |
| return False | |
| # Check for templated Step N patterns β these indicate generated reasoning | |
| # rather than genuine analytical thinking. Allow up to 2 step labels | |
| # (natural in analytical writing) but reject heavily templated outputs. | |
| step_labels = _STEP_LABEL_RE.findall(think_body) | |
| if len(step_labels) > 5: | |
| return False | |
| # Check for answer leakage: find the text after </think> and verify none | |
| # of its JSON values appear verbatim inside the think block. | |
| after_think = output_text[think_match.end():].strip() | |
| try: | |
| answer_obj = __import__("json").loads(after_think) | |
| for v in answer_obj.values(): | |
| if isinstance(v, str) and len(v) > 8 and v in think_body: | |
| # Tolerate short strings that appear as normal reasoning words; | |
| # flag only long values that indicate the answer was pre-written. | |
| if len(v) > 30: | |
| return False | |
| except Exception: | |
| pass # Non-JSON final answer β leakage check not applicable. | |
| return True | |
| think_body = think_match.group(1).strip() | |
| # Minimum word count inside the think block. | |
| if len(think_body.split()) < 30: | |
| return False | |
| # Must have at least 3 step labels. | |
| step_labels = _STEP_LABEL_RE.findall(think_body) | |
| if len(step_labels) < 3: | |
| return False | |
| # Check for answer leakage: find the text after </think> and verify none | |
| # of its JSON values appear verbatim inside the think block. | |
| after_think = output_text[think_match.end():].strip() | |
| try: | |
| answer_obj = __import__("json").loads(after_think) | |
| for v in answer_obj.values(): | |
| if isinstance(v, str) and len(v) > 8 and v in think_body: | |
| # Tolerate short strings that appear as normal reasoning words; | |
| # flag only long values that indicate the answer was pre-written. | |
| if len(v) > 30: | |
| return False | |
| except Exception: | |
| pass # Non-JSON final answer β leakage check not applicable. | |
| return True | |
| def _near_duplicate(a: str, b: str, threshold: float = 0.88) -> bool: | |
| """Return True when two strings are suspiciously similar (ratio >= threshold). | |
| Uses SequenceMatcher which is fast enough for the dataset sizes we see | |
| (hundreds to low thousands of examples). The threshold of 0.88 catches | |
| single-entity paraphrases (e.g. one node name swapped in an otherwise | |
| identical sentence, ratio ~0.90) while staying well above the ratio for | |
| genuinely distinct examples (unrelated edges typically score <0.3). | |
| """ | |
| from difflib import SequenceMatcher | |
| return SequenceMatcher(None, a, b, autojunk=False).ratio() >= threshold | |
| def curate_examples(examples: List[Dict[str, Any]]) -> List[Dict[str, Any]]: | |
| """Remove duplicates, near-duplicates, and low-signal examples before training. | |
| Pass 1 β Exact dedup: drop any example whose (instruction, input, output) | |
| triple has already been seen. | |
| Pass 2 β Near-dedup: for each surviving example, check its output against | |
| the last N accepted outputs using SequenceMatcher. If similarity | |
| >= 0.92 the example is skipped. We only compare against a sliding | |
| window of the most recent 200 outputs to keep runtime linear. | |
| Pass 3 β Quality floor: skip examples whose output has fewer than 4 words. | |
| Pass 4 β Thinking quality: for CoT/reasoning examples (those containing a | |
| <think> block), verify the block meets minimum structural standards | |
| so the model learns genuine reasoning rather than degenerate CoT. | |
| """ | |
| curated: List[Dict[str, Any]] = [] | |
| seen_exact: set = set() | |
| recent_outputs: List[str] = [] # sliding window for near-dup check | |
| WINDOW = 200 | |
| for example in examples: | |
| if not isinstance(example, dict): | |
| continue | |
| sanitized = sanitize_example(example) | |
| instruction = str(sanitized.get("instruction", "") or "").strip() | |
| input_text = str(sanitized.get("input", "") or "").strip() | |
| output_text = str(sanitized.get("output", "") or "").strip() | |
| if not instruction or not input_text or not output_text: | |
| continue | |
| if len(output_text.split()) < 4: | |
| continue | |
| # Pass 1 β exact dedup | |
| signature = (instruction.lower(), input_text.lower(), output_text.lower()) | |
| if signature in seen_exact: | |
| continue | |
| seen_exact.add(signature) | |
| # Pass 2 β near-dup check against recent outputs | |
| out_lower = output_text.lower() | |
| if any(_near_duplicate(out_lower, prev) for prev in recent_outputs[-WINDOW:]): | |
| continue | |
| # Pass 4 β CoT/thinking quality gate (no-op for non-reasoning examples) | |
| if not _thinking_quality_check(output_text): | |
| continue | |
| recent_outputs.append(out_lower) | |
| # Preserve task_type so format_prompt can detect reasoning examples | |
| # and route them through the thinking-safe formatter. | |
| entry: Dict[str, Any] = { | |
| "instruction": instruction, | |
| "input": input_text, | |
| "output": output_text, | |
| } | |
| if example.get("task_type"): | |
| entry["task_type"] = example["task_type"] | |
| curated.append(entry) | |
| return curated | |