File size: 6,318 Bytes
660e1b7
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
166e2f4
f2db534
421d869
 
9ff6bcc
 
 
4a98a27
7b82b69
97490d7
 
4f8a18c
660e1b7
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
#!/usr/bin/env python3
"""Answer-retention proxy for SOMA CoT compressors.

The competition score combines token reduction with tasks passed. Compression
ratio alone therefore says nothing about whether a compressor is any good. The
official check requires an LLM answering questions from the compressed context;
this is a cheaper LEXICAL PROXY and is explicitly not the official metric.

Method: for each reference answer, extract its content terms (alphanumeric
tokens >=4 chars, minus stopwords, plus any dotted/underscored identifiers and
quoted literals). Retention for one QA pair is the fraction of those terms that
still appear in the compressed context. A term that never appeared in the
UNCOMPRESSED context is excluded, since no compressor could have kept it -- this
makes the score measure compression damage rather than corpus coverage.
"""
from __future__ import annotations

import argparse
import importlib.util
import json
import re
import statistics
from pathlib import Path

PLUGIN = Path("/var/lib/octave/sn114/external/SOMA-plugin")
COT = Path(
    "/var/lib/octave/sn114/repo/miner/plain_text_compression/sample_tasks/"
    "cot_compression_tasks.jsonl"
)
COMPRESSORS = {
    "performance": "performance_focused_compressor.py",
    "aggressive": "aggressive_hybrid_compressor.py",
    "code_focused": "code_focused_compressor.py",
    "hybrid": "hybrid_compressor.py",
    "adaptive": "adaptive_compressor.py",
    "thinking_strip": "thinking_strip_compressor.py",
    "dialogue": "dialogue_focused_compressor.py",
    "structural": "structural_cot_compressor.py",
    "structural_reasoning": "structural_reasoning_compressor.py",
    "adaptive_structural": "adaptive_structural_compressor.py",
    "structural_lowcomp": "structural_lowcomp_compressor.py",
    "structural_selective": "structural_selective_compressor.py",
    "structural_selective_call": "structural_selective_call_compressor.py",
    "structural_pathkeep": "structural_pathkeep_compressor.py",
    "structural_threshold": "structural_threshold_compressor.py",
    "structural_perspan": "structural_perspan_compressor.py",
    "structural_forward": "structural_forward_compressor.py",
    "structural_combined": "structural_combined_compressor.py",
    "structural_gist": "structural_gist_compressor.py",
}
STOP = {
    "that","this","with","from","were","have","been","which","when","what","the",
    "and","for","was","are","not","but","its","their","there","then","than","into",
    "onto","after","before","because","would","could","should","also","such","only",
    "user","wanted","issue","problem","code","function","file","line","fix","fixed",
    "correctly","incorrectly","caused","causing","result","resulting","string",
}


def load(name: str, filename: str):
    spec = importlib.util.spec_from_file_location(f"r_{name}", PLUGIN / filename)
    mod = importlib.util.module_from_spec(spec)
    spec.loader.exec_module(mod)
    return mod.compress_messages


def content_terms(answer: str) -> set[str]:
    terms: set[str] = set()
    # dotted / underscored identifiers and paths carry the most signal
    for ident in re.findall(r"[A-Za-z_][\w./]*[\w]", answer):
        if ("." in ident or "_" in ident or "/" in ident) and len(ident) >= 4:
            terms.add(ident.lower())
    for lit in re.findall(r"'([^']{2,40})'|\"([^\"]{2,40})\"", answer):
        val = (lit[0] or lit[1]).strip().lower()
        if len(val) >= 3:
            terms.add(val)
    for w in re.findall(r"[A-Za-z][A-Za-z0-9]{3,}", answer):
        lw = w.lower()
        if lw not in STOP:
            terms.add(lw)
    return terms


def retention(context: str, compressed: str, qa: list[dict]) -> dict:
    ctx = context.lower()
    comp = compressed.lower()
    per_q, kept_t, total_t = [], 0, 0
    for pair in qa:
        terms = content_terms(pair.get("answer", ""))
        # only terms the uncompressed context could have supplied
        avail = {t for t in terms if t in ctx}
        if not avail:
            continue
        kept = sum(1 for t in avail if t in comp)
        per_q.append(kept / len(avail))
        kept_t += kept
        total_t += len(avail)
    return {
        "questions_scored": len(per_q),
        "mean_per_question_retention": statistics.fmean(per_q) if per_q else 0.0,
        "min_per_question_retention": min(per_q) if per_q else 0.0,
        "micro_term_retention": kept_t / total_t if total_t else 0.0,
        "terms_available": total_t,
    }


def main() -> None:
    ap = argparse.ArgumentParser()
    ap.add_argument("--out", type=Path, required=True)
    args = ap.parse_args()

    tasks = [json.loads(l) for l in COT.read_text().splitlines() if l.strip()]
    results = {}

    for name, filename in COMPRESSORS.items():
        fn = load(name, filename)
        rows, ratios = [], []
        for task in tasks:
            src = task["source_text"]
            qa = task.get("qa") or []
            if not qa:
                continue
            out = fn([{"role": "user", "content": src}])
            comp = out[0]["content"] if out else ""
            rows.append(retention(src, comp, qa))
            ratios.append(len(comp) / len(src))

        comp_pct = (1 - statistics.fmean(ratios)) * 100
        mean_ret = statistics.fmean(r["mean_per_question_retention"] for r in rows)
        micro = statistics.fmean(r["micro_term_retention"] for r in rows)
        worst = min(r["min_per_question_retention"] for r in rows)
        results[name] = {
            "compression_percent": round(comp_pct, 3),
            "mean_answer_retention": round(mean_ret, 4),
            "micro_term_retention": round(micro, 4),
            "worst_question_retention": round(worst, 4),
            "tasks": len(rows),
            "questions_scored": sum(r["questions_scored"] for r in rows),
            # joint view: retention weighted by how much was removed
            "retention_x_compression": round(mean_ret * (comp_pct / 100), 4),
        }
        print(f"{name:<16} comp={comp_pct:6.2f}%  retention={mean_ret:.4f}  "
              f"worst_q={worst:.4f}")

    args.out.write_text(json.dumps(
        {"metric": "lexical answer-retention proxy (NOT the official LLM-judged score)",
         "results": results}, indent=2))
    print(f"\nwrote {args.out}")


if __name__ == "__main__":
    main()