File size: 4,372 Bytes
f7d1bd2
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
5bc32b2
f7d1bd2
 
5bc32b2
 
 
 
 
 
f7d1bd2
 
 
 
 
 
5bc32b2
 
 
f7d1bd2
5bc32b2
f7d1bd2
 
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
"""
segmenting.py — Passage segmentation, classification, and fidelity gates for the
tessera-compressor harness.

Extracted from the harness the compressor was accepted under (same functions the
teacher mint used). Pure text processing: no network, no credentials.

Flow: segment -> group_steps -> classify_passage per passage -> model call ->
gate -> rules fallback on failure. A failed passage costs a few dozen tokens of
savings, never content.
"""
import re

CJK = re.compile(r'[一-鿿㐀-䶿]')
NUM = re.compile(r'\d+(?:\.\d+)?')
IDENT = re.compile(r'`[^`\n]+`|\b[A-Za-z]+(?:_[A-Za-z0-9]+)+\b|\b[a-z]+[A-Z][A-Za-z0-9]*\b')
FENCE = re.compile(r'```.*?```', re.DOTALL)
SENT_SPLIT = re.compile(r'(?<=[.!?;])\s+')
_LIST_MARKER = re.compile(r'(?:^|[\n\s(])(\d{1,2})[.)]\s')
_OPS = set('+-*/=<>≤≥≠∈∀∃¬→⇒%^{}[]')


def segment(text):
    """Split a reasoning block into ordered segments; code fences are atomic and marked."""
    segs = []  # (kind, text) kind ∈ {'code','prose'}
    pos = 0
    for m in FENCE.finditer(text):
        before = text[pos:m.start()]
        segs.extend(('prose', s) for s in _split_prose(before))
        segs.append(('code', m.group(0)))
        pos = m.end()
    segs.extend(('prose', s) for s in _split_prose(text[pos:]))
    return [(k, s) for k, s in segs if s.strip()]


def _split_prose(text):
    out = []
    for line in text.split('\n'):
        line = line.strip()
        if not line:
            continue
        out.extend(s.strip() for s in SENT_SPLIT.split(line) if s.strip())
    return out


def group_steps(segs, max_words=160, max_sents=10):
    """Merge consecutive prose sentences into step-sized passages; code stays atomic."""
    out, buf, words = [], [], 0

    def flush():
        nonlocal buf, words
        if buf:
            out.append(('prose', ' '.join(buf)))
            buf, words = [], 0

    for kind, s in segs:
        if kind == 'code':
            flush()
            out.append((kind, s))
            continue
        buf.append(s)
        words += len(s.split())
        if words >= max_words or len(buf) >= max_sents:
            flush()
    flush()
    return out


def facts(s):
    """Numbers + identifiers that must survive compression.
    List-enumeration markers ("1. Load...") are structure, not facts."""
    nums = set(NUM.findall(s)) - set(_LIST_MARKER.findall(s))
    idents = set(i.strip('`') for i in IDENT.findall(s))
    return nums | idents


def facts_preserved(src, out):
    """Substring presence — regex \\b breaks against adjacent CJK chars.
    Returns the list of MISSING facts (empty list = all preserved)."""
    out_n = out.replace(',', '')
    return [f for f in facts(src) if f.replace(',', '') not in out_n]


def classify_passage(seg, seen_facts, ntok):
    """'load' = fact-dense or novel-fact-bearing (step-faithful treatment);
       'narr' = search/narrative (stub treatment).
    ntok is a callable: text -> token count under your target tokenizer."""
    f = facts(seg)
    novel = f - seen_facts
    toks = max(ntok(seg), 1)
    dens = (len(NUM.findall(seg)) + len(IDENT.findall(seg))
            + sum(seg.count(o) for o in _OPS)) / toks
    if novel and (dens >= 0.08 or len(novel) >= 3):
        return 'load'
    if dens >= 0.15:
        return 'load'
    return 'narr'


def gate(src_seg, rules_seg, out, ntok, novel=None):
    """Deterministic per-passage fidelity gate.
    Returns None if the model output is admissible, else a short fail-reason
    string; on failure the caller uses rules_seg instead.

    novel: the passage's facts that are NOT already in the accumulated chain.
    The prompt tells the model never to restate chain content, so only novel
    facts are required to survive (matching the acceptance harness). Pass None
    to require every fact of the passage (stricter, for chainless use)."""
    if not out or not out.strip():
        return "empty"
    if '```' in out:
        return "fence"
    if len(out) > 2 * len(src_seg) + 40:           # explanation/blow-up guard
        return "blowup"
    required = facts(src_seg) if novel is None else novel
    out_n = out.replace(',', '')
    if any(f.replace(',', '') not in out_n for f in required):
        return "facts"
    if ntok(out) > ntok(rules_seg):                # must not exceed the rules-only version
        return "tokens"
    return None