summaryrefslogtreecommitdiff
path: root/src/gap_pipeline/surface.py
diff options
context:
space:
mode:
authorAnonymous Authors <anonymous@invalid.example>2026-07-24 13:52:11 -0500
committerAnonymous Authors <anonymous@invalid.example>2026-07-24 13:52:11 -0500
commitd4eb26780a8a8c70ca75812af0c3c6a295c0797c (patch)
tree46d2aa0fca13f6db2c8226471981ce06c1423eac /src/gap_pipeline/surface.py
parentdb293f3606a97b3e417de27124858e134005acbd (diff)
Restore original GAP prompts and lock prompt bytes
Diffstat (limited to 'src/gap_pipeline/surface.py')
-rw-r--r--src/gap_pipeline/surface.py211
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)]