feat(tooling): always log retry reasons in gemma_naming.py (#833)

Retry rejection lines (dedup, blocklist, placeholder, stem_cap, empty,
error) now print unconditionally, not only under --verbose. The
fallback line also includes a tally of the rejection reasons that
exhausted all attempts, e.g.:

    fallback: GJ144e-1/range_43 → 'Kirkwood Spine'  [blocklist=2 dedup=1]

Diagnostic run on 20 bodies confirms dedup is the primary fallback
driver. Gemma converges on a narrow set of range names ("The Ridge",
"Blackwood Range", "The Spine") that collide across bodies in the
same corridor. Blocklist catches "Thames" and "The Great Divide"
correctly. Zero stem-cap or subprocess-error fallbacks observed.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-16 08:20:36 +02:00
co-authored by Claude Opus 4.6
parent d5a437ea2d
commit e0707d8072
+17 -16
View File
@@ -1401,11 +1401,11 @@ def name_feature(
for stem in _extract_stems(final):
stem_counts[stem] = stem_counts.get(stem, 0) + 1
rejections: dict[str, int] = {"empty": 0, "placeholder": 0, "blocklist": 0,
"dedup": 0, "stem_cap": 0, "error": 0}
for attempt in range(max_attempts):
seed = _seed_for(world_seed, body_id, local_id, attempt)
# Rotate the example pool per attempt so retries get a different
# prompt, not just a different seed — big variety payoff for a
# small model like Gemma 2 2B.
prompt = _build_prompt(
feature_type,
inflection=palette["inflection"],
@@ -1420,31 +1420,31 @@ def name_feature(
try:
raw = voice.request(prompt, seed)
except RuntimeError as e:
if verbose:
log(f" subprocess error on {body_id}/{local_id} attempt "
f"{attempt}: {e} — retrying")
rejections["error"] += 1
log(f" error {body_id}/{local_id} attempt {attempt}: {e}")
continue
cleaned = post_process(raw)
if not cleaned:
rejections["empty"] += 1
log(f" empty {body_id}/{local_id} attempt {attempt}")
continue
if is_placeholder(cleaned):
if verbose:
log(f" placeholder '{cleaned}' ({body_id}/{local_id}) — retrying")
rejections["placeholder"] += 1
log(f" placeholder '{cleaned}' {body_id}/{local_id}")
continue
if is_blocked(cleaned, blocklist):
if verbose:
log(f" blocklist hit '{cleaned}' ({body_id}/{local_id}) — retrying")
rejections["blocklist"] += 1
log(f" blocklist '{cleaned}' {body_id}/{local_id}")
continue
if _is_duplicate(cleaned):
if verbose:
log(f" dedup hit '{cleaned}' ({body_id}/{local_id}) — retrying")
rejections["dedup"] += 1
log(f" dedup '{cleaned}' {body_id}/{local_id}")
continue
over_stem = _exceeds_stem_cap(cleaned)
if over_stem is not None:
if verbose:
log(f" stem cap hit '{cleaned}' (stem '{over_stem}' at "
f"cap {stem_cap}) {body_id}/{local_id} — retrying")
rejections["stem_cap"] += 1
log(f" stem_cap '{cleaned}' ('{over_stem}' {stem_cap}) {body_id}/{local_id}")
continue
_commit_name(cleaned)
return cleaned
@@ -1461,7 +1461,8 @@ def name_feature(
fallback = fallback_name(corridor, feature_type, salt + bump)
used.add(fallback)
body_used.add(fallback)
log(f" fallback: {body_id}/{local_id}'{fallback}'")
reasons = " ".join(f"{k}={v}" for k, v in rejections.items() if v > 0)
log(f" fallback: {body_id}/{local_id}'{fallback}' [{reasons}]")
return fallback