"""Chunk snapshot pages by heading into OKF units. A chunk = one coherent doc section: its heading path, its prose, and any code blocks that live under it. API pages also carry their [source] GitHub link as metadata. Units are emitted as OKF files (YAML frontmatter + markdown body) — the human/agent-readable knowledge snapshot — which the embed step then loads into Neon. """ from __future__ import annotations import re from dataclasses import dataclass, field from pathlib import Path import yaml HEADING_RE = re.compile(r"^(#{1,6})\s+(.*)$", re.MULTILINE) GITHUB_SOURCE_RE = re.compile(r"https://github\.com/pytorch/[\w.-]+/blob/\S+") # Sphinx headerlinks survive HTML→markdown as e.g. [¶](#sgd "Permalink...") — # they carry the TRUE anchor, and must not leak into titles/heading paths HEADERLINK_RE = re.compile(r"\[[^\]]*\]\(#([^)\s\"]+)[^)]*\)") # A section bigger than this is cut into parts at natural seams. Aligned with # index/embed.MAX_EMBED_CHARS: anything past it was invisible to the dense # vector anyway (embedded blind, silently). Each part inherits the section's # heading_path, so indexed_text() prepends the same symbol+heading "synopsis" # to every part — the serialized-story recap — and all parts share the # section's URL+anchor, so citations stay exact. CHUNK_TARGET_CHARS = 2000 _FENCE_RE = re.compile(r"```.*?```", re.DOTALL) @dataclass class Section: heading_path: list[str] title: str text: str anchor: str = "" source_link: str = field(default="") def slugify(title: str) -> str: """Sphinx-style anchor slug: lowercase, alphanumerics, hyphens.""" return re.sub(r"[^a-z0-9]+", "-", title.lower()).strip("-") def clean_heading(raw_title: str) -> tuple[str, str]: """(clean title, anchor): take the real Sphinx anchor from the headerlink when present, strip the link markup from the title, unescape markdown.""" match = HEADERLINK_RE.search(raw_title) title = HEADERLINK_RE.sub("", raw_title).replace("\\_", "_").strip() anchor = match.group(1) if match else slugify(title) return title, anchor def split_by_heading(markdown: str) -> list[Section]: """Split a page into sections at every heading; preamble becomes section 0.""" matches = list(HEADING_RE.finditer(markdown)) sections: list[Section] = [] stack: list[tuple[int, str]] = [] # (level, title) breadcrumbs preamble = markdown[: matches[0].start()].strip() if matches else markdown.strip() if preamble: sections.append(Section(heading_path=[], title="", text=preamble)) for i, match in enumerate(matches): level = len(match.group(1)) title, anchor = clean_heading(match.group(2).strip()) end = matches[i + 1].start() if i + 1 < len(matches) else len(markdown) text = markdown[match.end() : end].strip() while stack and stack[-1][0] >= level: stack.pop() stack.append((level, title)) source = GITHUB_SOURCE_RE.search(text) sections.append( Section( heading_path=[t for _, t in stack], title=title, text=text, anchor=anchor, source_link=source.group(0) if source else "", ) ) return sections def _atoms(text: str) -> list[str]: """Paragraphs of the text, with fenced code blocks kept whole. A naive split on blank lines would cut inside ``` fences (code often has blank lines); half a code block is noise to embed. Fences are atoms. """ atoms: list[str] = [] last = 0 for match in _FENCE_RE.finditer(text): atoms += [p for p in text[last : match.start()].split("\n\n") if p.strip()] atoms.append(match.group(0)) last = match.end() atoms += [p for p in text[last:].split("\n\n") if p.strip()] return atoms def _hard_split(atom: str, limit: int) -> list[str]: """Last resort for one atom bigger than the whole budget: cut at line ends.""" parts: list[str] = [] current = "" for line in atom.splitlines(keepends=True): while len(line) > limit: # a single enormous line — slice it parts.append(line[:limit]) line = line[limit:] if current and len(current) + len(line) > limit: parts.append(current) current = line else: current += line if current.strip(): parts.append(current) return [p.strip("\n") for p in parts if p.strip()] def split_oversized(text: str, limit: int | None = None) -> list[str]: """Cut oversized section text into parts at natural seams. Greedy paragraph packing: whole paragraphs (and whole code fences) are accumulated until the next one would overflow the limit; only an atom that alone exceeds the limit is cut inside (at line boundaries). """ if limit is None: limit = CHUNK_TARGET_CHARS if len(text) <= limit: return [text] pieces: list[str] = [] for atom in _atoms(text): pieces.extend(_hard_split(atom, limit) if len(atom) > limit else [atom]) parts: list[str] = [] current = "" for piece in pieces: if current and len(current) + 2 + len(piece) > limit: parts.append(current) current = piece else: current = f"{current}\n\n{piece}" if current else piece if current: parts.append(current) return parts def page_kind(url: str) -> str: if "/tutorials/" in url: return "tutorial" if "/generated/" in url or "/docs/" in url: return "api" return "guide" # a signature-shaped paragraph: `class torch.nn.Linear(...)`, `torch.add(...)` _SIGNATURE_START_RE = re.compile(r"^(class\s+|@)?[\w.]+\(") _SENTENCE_RE = re.compile(r"(.+?[.!?])(?:\s|$)") _MD_LINK_RE = re.compile(r"\[([^\]]*)\]\([^)]*\)") # [text](url) → text SYNOPSIS_MAX_CHARS = 240 def _plain(text: str) -> str: """Markdown → comparable prose: links keep their text, emphasis dropped.""" return " ".join(_MD_LINK_RE.sub(r"\1", text).replace("*", "").replace("\\_", "_").split()) def page_synopsis(body: str, limit: int = SYNOPSIS_MAX_CHARS) -> str: """The page's own first prose sentence — the docstring summary a human wrote. Reference pages are signature/parameter-shaped, so their embedding sits far from descriptive questions even though the description sentence ("This criterion computes the cross entropy loss between input logits and target") usually exists — buried. Extracting it deterministically (no LLM, no quota, no hallucination) lets indexed_text() lead every chunk of the page with it. Shape gotcha this handles: markdownify renders Sphinx's signature