diff options
| author | Anonymous Authors <anonymous@invalid.example> | 2026-07-24 13:52:11 -0500 |
|---|---|---|
| committer | Anonymous Authors <anonymous@invalid.example> | 2026-07-24 13:52:11 -0500 |
| commit | d4eb26780a8a8c70ca75812af0c3c6a295c0797c (patch) | |
| tree | 46d2aa0fca13f6db2c8226471981ce06c1423eac /src/gap_pipeline/surface.py | |
| parent | db293f3606a97b3e417de27124858e134005acbd (diff) | |
Restore original GAP prompts and lock prompt bytes
Diffstat (limited to 'src/gap_pipeline/surface.py')
| -rw-r--r-- | src/gap_pipeline/surface.py | 211 |
1 files changed, 65 insertions, 146 deletions
diff --git a/src/gap_pipeline/surface.py b/src/gap_pipeline/surface.py index 7de4e90..914d847 100644 --- a/src/gap_pipeline/surface.py +++ b/src/gap_pipeline/surface.py @@ -1,16 +1,14 @@ -"""Surface-renaming generation, application, and deterministic validation.""" +"""Surface-renaming generation using the verbatim original prompts.""" from __future__ import annotations -import asyncio -import hashlib import re from dataclasses import dataclass from typing import Literal from .clients import JsonLLM from .models import CanonicalItem, ModelCallRecord -from .prompts import SURFACE_NAME_SYSTEM, surface_name_user +from .prompts import surface_system, surface_user from .store import RunStore @@ -28,99 +26,49 @@ SURFACE_FAMILIES: tuple[SurfaceFamily, ...] = ( "garbled_string", ) -IDENTIFIER_RE = re.compile(r"^[A-Za-z][A-Za-z0-9]{2,63}$") -MATH_SPAN_RE = re.compile( - r"(?P<dollar>\${1,2}.*?\${1,2})" - r"|(?P<paren>\\\(.*?\\\))" - r"|(?P<bracket>\\\[.*?\\\])" - r"|(?P<env>\\begin\{(?P<envname>[A-Za-z*]+)\}.*?\\end\{(?P=envname)\})", - flags=re.DOTALL, -) +IDENTIFIER_RE = re.compile(r"^[a-z]{8,}$") @dataclass(frozen=True) class SurfaceVariant: family: SurfaceFamily rename_map: dict[str, str] - problem: str + question: str solution: str def as_release_payload(self) -> dict[str, object]: return { "map": dict(self.rename_map), - "question": self.problem, + "question": self.question, "solution": self.solution, } -def validate_rename_map( - rename_map: dict[str, str], +def validate_surface_variant( + item: CanonicalItem, *, - existing_identifiers: list[str], - scientific_constants: list[str], + rename_map: dict[str, str], + question: str, + solution: str, ) -> None: - if not rename_map: - raise ValueError("surface rename map cannot be empty") + expected = set(item.variables + item.parameters) + if set(rename_map) != expected: + raise ValueError( + "rename map must contain every var and param exactly once; " + f"expected {sorted(expected)}, received {sorted(rename_map)}" + ) if len(rename_map.values()) != len(set(rename_map.values())): raise ValueError("surface replacements must be one-to-one") - originals = set(existing_identifiers) | set(scientific_constants) - for old, new in rename_map.items(): - if old not in existing_identifiers: - raise ValueError(f"rename source {old!r} is not in the symbol inventory") - if new in originals: - raise ValueError(f"replacement {new!r} collides with an existing identifier") - if not IDENTIFIER_RE.fullmatch(new): - raise ValueError(f"invalid replacement identifier {new!r}") - if new.startswith("\\"): - raise ValueError("replacement may not be a LaTeX command") - - -def _replace_in_span(span: str, rename_map: dict[str, str]) -> str: - result = span - for old in sorted(rename_map, key=lambda value: (-len(value), value)): - # ASCII identifier boundaries avoid r -> radius changing \sqrt or prose. - pattern = re.compile( - rf"(?<![A-Za-z0-9]){re.escape(old)}(?![A-Za-z0-9])" - ) - result = pattern.sub(lambda _: rename_map[old], result) - return result - - -def apply_rename_map(text: str, rename_map: dict[str, str]) -> str: - """Rename identifiers only inside explicit LaTeX math spans. - - This deliberately avoids the old prototype's ``a`` -> identifier mutation - in ordinary English prose. PutnamGAP source records use explicit math - delimiters for mathematical identifiers. - """ - - output: list[str] = [] - cursor = 0 - for match in MATH_SPAN_RE.finditer(text): - output.append(text[cursor : match.start()]) - output.append(_replace_in_span(match.group(0), rename_map)) - cursor = match.end() - output.append(text[cursor:]) - return "".join(output) - - -def create_surface_variant( - item: CanonicalItem, - family: SurfaceFamily, - rename_map: dict[str, str], -) -> SurfaceVariant: - existing = item.variables + item.parameters - validate_rename_map( - rename_map, - existing_identifiers=existing, - scientific_constants=item.scientific_constants, - ) - return SurfaceVariant( - family=family, - rename_map=dict(rename_map), - problem=apply_rename_map(item.problem, rename_map), - solution=apply_rename_map(item.solution, rename_map), - ) + forbidden = expected | set(item.scientific_constants) + for replacement in rename_map.values(): + if replacement in forbidden: + raise ValueError(f"replacement {replacement!r} collides with input symbols") + if not IDENTIFIER_RE.fullmatch(replacement): + raise ValueError( + f"replacement {replacement!r} violates the original prompt contract" + ) + if not question.strip() or not solution.strip(): + raise ValueError("surface question and solution must be non-empty") class SurfacePipeline: @@ -128,90 +76,61 @@ class SurfacePipeline: self.proposer = proposer self.store = store - async def propose_map( + async def run_family( self, item: CanonicalItem, family: SurfaceFamily, - ) -> dict[str, str]: - symbols = [(value, "free variable") for value in item.variables] + [ - (value, "fixed parameter") for value in item.parameters - ] - forbidden = list( - dict.fromkeys( - item.variables + item.parameters + item.scientific_constants - ) + ) -> SurfaceVariant: + request_id = f"{item.item_id}.surface.{family}" + system_prompt = surface_system(family) + user_prompt = surface_user(item) + response = await self.proposer.generate_json( + system_prompt=system_prompt, + user_prompt=user_prompt, + request_id=request_id, ) - - async def propose(symbol: str, role: str) -> tuple[str, str]: - request_id = f"{item.item_id}.surface.{family}.{symbol}" - user_prompt = surface_name_user( - symbol=symbol, - role=role, - family=family, - context=(item.problem + "\n" + item.solution)[:12_000], - forbidden=forbidden, - ) - response = await self.proposer.generate_json( - system_prompt=SURFACE_NAME_SYSTEM, - user_prompt=user_prompt, + self.store.write_call( + ModelCallRecord( request_id=request_id, + model=response.model, + system_prompt=system_prompt, + user_prompt=user_prompt, + response_data=response.data, + raw_text=response.raw_text, + provider_response_id=response.response_id, + usage=response.usage, ) - self.store.write_call( - ModelCallRecord( - request_id=request_id, - model=response.model, - system_prompt=SURFACE_NAME_SYSTEM, - user_prompt=user_prompt, - response_data=response.data, - raw_text=response.raw_text, - provider_response_id=response.response_id, - usage=response.usage, - ) - ) - replacement = str(response.data["replacement"]) - return symbol, replacement - - proposals = await asyncio.gather( - *(propose(symbol, role) for symbol, role in symbols) ) - rename_map = dict(proposals) - validate_rename_map( - rename_map, - existing_identifiers=item.variables + item.parameters, - scientific_constants=item.scientific_constants, + rename_map = { + str(old): str(new) + for old, new in dict(response.data["map"]).items() + } + question = str(response.data["question"]) + solution = str(response.data["solution"]) + validate_surface_variant( + item, + rename_map=rename_map, + question=question, + solution=solution, ) - self.store.write_stage( - f"surface_{family}_map", - {"rename_map": rename_map}, - request_id=None, + variant = SurfaceVariant( + family=family, + rename_map=rename_map, + question=question, + solution=solution, ) - return rename_map - - async def run_family( - self, - item: CanonicalItem, - family: SurfaceFamily, - ) -> SurfaceVariant: - rename_map = await self.propose_map(item, family) - variant = create_surface_variant(item, family, rename_map) self.store.write_stage( f"surface_{family}_variant", variant.as_release_payload(), - request_id=None, + request_id=request_id, ) return variant - async def run_all(self, item: CanonicalItem) -> dict[SurfaceFamily, SurfaceVariant]: - # Run families sequentially so the immutable call log stays easy to - # inspect; symbol proposals within each family remain concurrent. + async def run_all( + self, + item: CanonicalItem, + ) -> dict[SurfaceFamily, SurfaceVariant]: output: dict[SurfaceFamily, SurfaceVariant] = {} for family in SURFACE_FAMILIES: output[family] = await self.run_family(item, family) return output - - -def deterministic_garbled_name(item_id: str, symbol: str, length: int = 12) -> str: - """Stable offline GS name for smoke tests; production follows the proposer.""" - - digest = hashlib.sha256(f"{item_id}:{symbol}".encode()).hexdigest() - return "v" + digest[: max(3, length - 1)] |
