Spaces:
Running
Running
File size: 9,265 Bytes
4f740eb | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 | """Create: rephrase good source documents into new training documents, or write new ones where sources are thin.
Rephrase styles are the five from Sutra (weights 30/25/20/15/10, after BeyondWeb), plus math and code styles.
A fidelity check flags rephrasings that introduce numbers or names the source does not contain, which matters when the
training data must respect a knowledge cutoff.
"""
from __future__ import annotations
import random
import re
from .llm import Endpoint, EndpointError
NO_PREAMBLE = ("Output ONLY the rewritten document. No preamble, no meta-commentary, no phrases like \"Here is\", "
"\"Let me\", \"Sure\" or \"Okay\". Do not add facts, numbers, names or dates that are not in the original.")
STYLES = {
"conversational": ("Teacher-student dialogue", 30, """Rewrite the content as a natural conversation between a teacher and a student.
The student asks curious, natural questions; the teacher explains clearly with concrete examples taken from the content.
Keep all key information and the same technical accuracy. Keep a similar length (within 20%).
Use the labels **Teacher**: and **Student**: and start directly with "**Teacher**:"."""),
"tutorial": ("Step-by-step tutorial", 25, """Rewrite the content as a clear step-by-step tutorial.
Start with "## Tutorial: <topic>", then an overview, prerequisites, numbered steps with what to do and how to check it,
a short practice exercise and key takeaways. Keep all key information accurate. Keep a similar length (within 20%)."""),
"qa": ("Questions and answers", 20, """Rewrite the content as 4 to 7 questions and thorough answers that together cover every key point,
moving from basic (what) to deeper (why, how) to applied. Start with "## Understanding <topic>: Questions and Answers".
Use **Q:** and **A:** labels. Keep a similar total length (within 20%)."""),
"dense_reference": ("Dense reference notes", 15, """Rewrite the content as compact reference notes at 70 to 85 percent of the original length.
Remove filler and repetition. Use headings and bullet points: core definition, key properties, relationships,
essential examples, formulas or procedures if any, pitfalls."""),
"pedagogical": ("Textbook section", 10, """Rewrite the content as a well-structured textbook section: learning objectives, the connection to what
the reader already knows, the core explanation, one worked example with step-by-step reasoning, a common question
with its answer, and a quick self-check question. Keep a similar length."""),
"worked_examples": ("Worked problems (math)", 0, """Rewrite the content as 2 to 4 worked problems that use exactly the ideas in it. For each: state the problem,
solve it step by step with the reasoning for each step, and check the answer. Use LaTeX for mathematics."""),
"code_walkthrough": ("Code walkthrough (code)", 0, """Rewrite the content as an explained code walkthrough: what the code does, then the code in a fenced block
(keep it correct and runnable; you may tidy names and add comments), then an explanation of the key lines and one
example of using it."""),
}
KIND_STYLES = {"math": ["worked_examples", "pedagogical", "qa", "tutorial"],
"code": ["code_walkthrough", "tutorial", "qa", "dense_reference"]}
def styles_for(kind: str) -> list[str]:
return KIND_STYLES.get(kind, ["conversational", "tutorial", "qa", "dense_reference", "pedagogical"])
def pick_style(kind: str, rng: random.Random, allowed: list[str] | None = None) -> str:
opts = [s for s in (allowed or styles_for(kind)) if s in STYLES]
weights = [max(STYLES[s][1], 10) for s in opts]
return rng.choices(opts, weights=weights, k=1)[0]
def chunks(text: str, max_chars: int = 6000) -> list[str]:
"""Split a long document at paragraph breaks so each rewrite sees a piece it can keep at a similar length."""
if len(text) <= max_chars:
return [text]
out, cur = [], ""
for para in re.split(r"\n\s*\n", text):
if cur and len(cur) + len(para) + 2 > max_chars:
out.append(cur)
cur = ""
cur = (cur + "\n\n" + para) if cur else para
while len(cur) > max_chars: # one giant paragraph
out.append(cur[:max_chars])
cur = cur[max_chars:]
if cur.strip():
out.append(cur)
return [c for c in out if len(c.split()) >= 80] or [text[:max_chars]]
def rephrase_prompt(text: str, style: str, language: str = "") -> str:
lang = f"\nWrite it in {language}." if language and language.lower() != "same" else ""
return f"""{STYLES[style][2]}{lang}
{NO_PREAMBLE}
Original content:
<<<
{text[:8000]}
>>>"""
LEAD = re.compile(r"^(here('s| is)|sure|okay|certainly|let me|i('ll| will))\b", re.I)
NUM = re.compile(r"\b\d[\d,.]*\b")
# a capitalized word that follows a lowercase word mid-sentence: names, places, products. Words at the start of a
# sentence, a line or a markdown label are skipped, so "Teacher:" or "Great question" never count.
HEADINGS = re.compile(r"^#+ .*$|\*\*[^*\n]{1,80}\*\*", re.M)
NAME = re.compile(r"\b[a-z][a-z'-]*[,;]? ([A-Z][a-zA-Z]{2,}(?: [A-Z][a-zA-Z]{2,})*)")
def fidelity(src: str, out: str, template: str = "") -> dict:
"""Share of numbers and mid-sentence names in the rewrite that never appear in the source. A rough proxy for
invented facts; the threshold is a choice, not a law."""
s_low = (src + " " + template).lower()
body = HEADINGS.sub(" ", out) # headings and bold labels the style asks for are not new facts
nums = {n.rstrip(".,") for n in NUM.findall(out)}
names = set(NAME.findall(body))
new_nums = [n for n in nums if n and n not in src]
new_names = [n for n in names if n.lower() not in s_low]
total = len(nums) + len(names)
share = (len(new_nums) + len(new_names)) / total if total else 0.0
return {"new_numbers": sorted(new_nums)[:8], "new_names": sorted(new_names)[:8], "new_share": round(share, 3),
"checked": total}
def rephrase(ep: Endpoint, text: str, style: str, language: str = "", max_new_share: float = 0.35) -> dict:
"""One rewrite. Returns {"text", "style", "fidelity", "ok", "why"}."""
def ok(s):
return len(s.split()) >= 60 and not LEAD.search(s.strip()[:40])
try:
out = ep.chat(rephrase_prompt(text, style, language), max_tokens=3000, temperature=0.7, retries=3, validate=ok)
except EndpointError as e:
return {"text": "", "style": style, "ok": False, "why": f"model call failed: {e}"[:200]}
fid = fidelity(text, out, STYLES[style][2])
new = len(fid["new_numbers"]) + len(fid["new_names"])
# invented numbers are the clearest sign of made-up content; one stray number or name is not a pattern
good = len(fid["new_numbers"]) < 3 and (new < 2 or fid["new_share"] <= max_new_share)
return {"text": out, "style": style, "fidelity": fid, "ok": good, "why": "" if good else "adds facts the source does not contain"}
# ---------------------------------------------------------------- new documents where sources are thin
DOC_TYPES = { # a subset of Sutra's content types that reads well as pretraining text
"concept_introduction": "an introduction to the concept: definition, intuition, why it matters, how it works, two examples, a common misconception, and how it connects to related ideas",
"reasoning_demonstration": "a worked demonstration of reasoning about a concrete problem in this area, thinking step by step and checking the result",
"worked_examples": "two or three worked examples of increasing difficulty, each solved step by step",
"common_misconceptions": "an explanation of the most common misconceptions about the concept, why people hold them, and the correct understanding",
"problem_set": "a short problem set of four problems with full solutions",
"case_study": "a realistic case study that applies the concept, with the decisions and their reasons",
"technical_documentation": "clear technical documentation of the concept or tool, with a usage example",
"historical_context": "the history of how the idea developed, who contributed what, and why it mattered",
}
def invent_prompt(topic: str, doc_type: str, level: int, request: str, language: str = "English") -> str:
return f"""You are writing high-quality pretraining text for a language model. Collection: "{request}".
Write {DOC_TYPES[doc_type]}.
Concept: {topic}
Difficulty: {level} on a 1 (introductory) to 10 (expert) scale. Language: {language}.
Write 350 to 700 words of dense, accurate prose with markdown headings where useful. Every sentence should teach
something. Do not invent statistics, quotes or citations. {NO_PREAMBLE.split('. Do not add')[0]}."""
def invent(ep: Endpoint, topic: str, doc_type: str, level: int, request: str, language: str = "English") -> dict:
def ok(s):
return len(s.split()) >= 150 and not LEAD.search(s.strip()[:40])
try:
out = ep.chat(invent_prompt(topic, doc_type, level, request, language), max_tokens=2500, temperature=0.8, retries=3, validate=ok)
except EndpointError as e:
return {"text": "", "ok": False, "why": f"model call failed: {e}"[:200]}
return {"text": out, "ok": True, "doc_type": doc_type, "topic": topic, "level": level}
|