Files
iolai26-solve/solver/scaffold.py
ModelHub XC 5b016c1af1 初始化项目,由ModelHub XC社区提供模型
Model: rpant/iolai26-solve
Source: Original Platform
2026-07-28 09:36:12 +08:00

114 lines
4.6 KiB
Python

"""Deterministic analysis blocks (segmentation, alignment, numerals) for the
prompt: the full scaffold and the optional light hint."""
from __future__ import annotations
from typing import Dict, List, Optional, Sequence, Tuple
from .align import align as build_align
from .numerals import extract_attested, induce
from .preprocess import Puzzle, strip_punct, tokenize
from .segment import Segmenter
MAX_SEG_LINES = 40
MAX_ALIGN_LINES = 30
MAX_VOCAB = 60
def analysis_blocks(puzzle: Puzzle) -> Tuple[str, str]:
"""(segmentation block, alignment block) for the prompt. Also used by the
CEGIS proposer (synth.py)."""
amap = build_align(puzzle.pairs)
align_lines = []
for tok, cands in sorted(amap.items()):
top = [f"{t} ({s:.1f})" for t, s in cands[:2] if s > 0.2]
if top:
align_lines.append(f" {tok} ~ {', '.join(top)}")
vocab, groups = [], {}
for p in puzzle.pairs:
for t in tokenize(p.src):
t = strip_punct(t).casefold()
if t and t not in vocab:
vocab.append(t)
vocab = vocab[:MAX_VOCAB]
for tok, cands in amap.items():
if cands:
groups.setdefault(cands[0][0], set()).add(tok)
seg = Segmenter().fit(vocab, share_groups=[g for g in groups.values() if len(g) > 1])
seg_lines = []
for w in vocab:
parts = seg.segment(w)
if len(parts) > 1:
seg_lines.append(f" {w} = {'-'.join(parts)}")
return ("\n".join(seg_lines[:MAX_SEG_LINES]) or " (none found)",
"\n".join(align_lines[:MAX_ALIGN_LINES]) or " (none found)")
def light_hint(puzzle: Puzzle, max_lines: int = 10) -> str:
"""A minimal, optional hint for the lean prompt: a few morpheme segmentation
guesses, framed as fallible. Off by default; enabled via a toggle."""
try:
seg_block, _align = analysis_blocks(puzzle)
except Exception:
return ""
lines = [l for l in seg_block.splitlines() if l.strip() and "none found" not in l]
if not lines:
return ""
body = "\n".join(lines[:max_lines])
return ("Optional hint (an automatic guess at word parts; it may be wrong, "
"so rely on the data itself):\n" + body)
def numeral_block(puzzle: Puzzle) -> str:
"""Induced numeral-system values, when the CSP solved and round-trip
verified them — the strongest kind of hint we can give."""
if puzzle.task_type not in ("text_to_num", "num_to_text"):
return ""
attested = extract_attested(puzzle.pairs)
system = induce(attested) if attested else None
if system is None:
return ""
vals = ", ".join(f"{t}={v}" for t, v in sorted(system.values.items(), key=lambda kv: kv[1]))
return (f"Numeral analysis (verified against every attested example):\n {vals}\n"
f" combination rule: a smaller value directly before a larger one multiplies it; "
f"otherwise values add.")
def candidate_block(items_answers: Sequence[Tuple[str, Optional[str], float, str]]) -> str:
"""Symbolic candidate answers per item: (item label, answer, confidence,
method). Only candidates with real evidence are shown — a low-confidence
echo would anchor the model on garbage."""
lines = []
for label, ans, conf, method in items_answers:
if ans and conf >= 0.4:
lines.append(f" item {label}: '{ans}' (source: {method}, fit {conf:.2f})")
if not lines:
return ""
return ("Candidate answers from mechanical analysis (adopt if consistent with "
"the data, correct if not):\n" + "\n".join(lines))
def build_scaffold(puzzle: Puzzle,
answers: Optional[Sequence[Optional[str]]] = None,
confs: Optional[Sequence[float]] = None,
methods: Optional[Sequence[str]] = None) -> str:
"""Full scaffold block for one puzzle's prompt."""
seg_block, align_block = analysis_blocks(puzzle)
parts = [
"## Mechanical analysis (computed from the data above; may contain errors — "
"the attested data always wins)",
f"Morpheme segmentation hypotheses:\n{seg_block}",
f"Word alignment hypotheses (task-language token ~ likely meaning):\n{align_block}",
]
nb = numeral_block(puzzle)
if nb:
parts.append(nb)
if answers is not None and confs is not None:
labels = [it.number or str(i + 1) for i, it in enumerate(puzzle.items)]
meths = list(methods) if methods else ["symbolic"] * len(labels)
cb = candidate_block(list(zip(labels, answers, confs, meths)))
if cb:
parts.append(cb)
return "\n\n".join(parts)