mtapi / app /grouping.py
Cnass's picture
Upload 6 files
4112c61 verified
Raw History Blame Contribute Delete
2.3 kB
"""Pack several strings into one generation and unpack the result.
Translating 50k game strings one request at a time pays the instruction
prompt's cost once per string and gives the model no idea what came before,
which is where pronoun/name drift in dialogue comes from. Sending a numbered
group instead amortises the instruction and lets the model see the
surrounding lines.
The risk is the model dropping, merging or renumbering a segment, so decoding
is strict: anything that does not come back with exactly the expected markers
is rejected and the caller re-translates that group one string at a time.
"""
import re
# Marker wrapping the segment index. Deliberately not a bracket form that
# collides with RPG Maker control codes (\V[1], \N[1], %1): the model has to
# copy these through untouched, and reusing the game's own syntax invites it
# to treat them as translatable content.
_MARKER = "<<{n}>>"
_MARKER_RE = re.compile(r"^[ \t]*<<\s*(\d+)\s*>>[ \t]?", re.MULTILINE)
def encode(texts: list[str]) -> str:
"""Render texts as a numbered list, one marker per segment."""
return "\n".join(
f"{_MARKER.format(n=i)} {text}" for i, text in enumerate(texts, start=1)
)
def decode(output: str, expected: int) -> list[str] | None:
"""Split a grouped translation back into segments.
Returns None when the output does not carry exactly the markers 1..expected
once each - that is the signal to fall back to per-string translation
rather than silently returning misaligned text.
"""
matches = list(_MARKER_RE.finditer(output))
if len(matches) != expected:
return None
segments: list[str] = []
for index, match in enumerate(matches):
if int(match.group(1)) != index + 1:
return None
start = match.end()
end = matches[index + 1].start() if index + 1 < len(matches) else len(output)
segments.append(output[start:end].strip("\n"))
if any(not segment.strip() for segment in segments):
return None
return segments
def chunk(texts: list[str], size: int) -> list[list[str]]:
"""Split texts into consecutive groups, preserving order."""
if size <= 1:
return [[text] for text in texts]
return [texts[i : i + size] for i in range(0, len(texts), size)]