#!/usr/bin/env python3 """Диагностика + демонстрация ДЕТЕРМИНИРОВАННОГО рычага коэффициента канон-консистентности (follow-up к adaptive_incontext.py по запросу владельца: «раз не идём в гибрид — долбить детерминированную сторону»). Два вопроса: 1. ДИАГНОЗ: промахи в arm B_glossary (напр. 王胡) — это РЕАЛЬНЫЙ провал adherence (модель не использовала утв. форму) или АРТЕФАКТ моего accept-регэкспа (напр. `Ван\b` не ловит склонённое «Вана/Вану»)? Печатаем реальный вывод + два матча (строгий \b vs склонение-толерантный стем). 2. РЫЧАГ: post-check находит промах → точечный re-ask («используй утв. формы X,Y») → повторный перевод → коэффициент до/после. Это verifier-gated single-pass correction (research/14 §6, M2) — тот самый детерминированный рычаг, а не дообучение. deepseek-v4-flash, тот же чанк 阿Q. INDICATIVE (малый N). Usage: eval/.venv/bin/python eval/adaptive_reask.py """ from __future__ import annotations import json, re, sys from pathlib import Path sys.path.insert(0, str(Path(__file__).resolve().parent)) import refusal_bench as rb ROOT = Path(__file__).resolve().parent SRC = ROOT / "data" / "samples" / "zh" / "luxun-ah-q-ch5-9.txt" CHUNK = SRC.read_text(encoding="utf-8")[6360:6360 + 650] # canonical dst + СТРОГИЙ (\b, как в adaptive_incontext) и СКЛОНЕНИЕ-ТОЛЕРАНТНЫЙ стем-матч ENT = { "阿Q": ("А-кью", r"А[-\s]?кью", r"А[-\s]?кью"), "未庄": ("Вэйчжуан", r"Вэйчжуан", r"Вэйчжуан"), "赵太爷": ("почтенный Чжао", r"Чжао", r"Чжао"), "王胡": ("Бородатый Ван", r"Ван\b", r"Ван[аоуые]?\b|Бород"), "小D": ("Маленький Дэн", r"Дэн", r"Дэн"), "假洋鬼子": ("Поддельный заморский чёрт", r"заморск\w+ чёрт|поддельн", r"заморск|поддельн|чёрт"), "吴妈": ("У-ма", r"У[-\s]?ма", r"У[-\s]?ма"), "邹七嫂": ("тётушка Цзоу Седьмая", r"Цзоу", r"Цзоу"), } SYSTEM = ("Ты профессиональный литературный переводчик. Переведи фрагмент на русский язык. " "Сохрани все реплики и детали; стиль — живой литературный русский. Выведи ТОЛЬКО перевод.") GLOSS = "ГЛОССАРИЙ (используй эти утверждённые переводы последовательно):\n" + \ "\n".join(f"{z} → {v[0]}" for z, v in ENT.items()) PRESENT = [z for z in ENT if z in CHUNK] def score(out, strict): idx = 1 if strict else 2 return [z for z in PRESENT if re.search(ENT[z][idx], out)] def translate(user): t, err, _ = rb.call_provider({"name": "deepseek", "base_url": "https://api.deepseek.com/v1", "model": "deepseek-v4-flash", "api_key_env": "DEEPSEEK_API_KEY", "max_tokens": 8000}, SYSTEM, user, timeout=180) return t or "" def main(): print(f"present entities ({len(PRESENT)}): {PRESENT}\n") for rep in range(2): print(f"================= REP {rep+1} =================") out = translate(GLOSS + "\n\nФРАГМЕНТ:\n" + CHUNK) strict, tol = score(out, True), score(out, False) print(f"[B_glossary] строгий-матч {len(strict)}/8={len(strict)/8:.3f} | склонение-толерантный {len(tol)}/8={len(tol)/8:.3f}") print(f" промах(строгий): {[z for z in PRESENT if z not in strict]}") print(f" промах(толерантный, = РЕАЛЬНЫЕ провалы adherence): {[z for z in PRESENT if z not in tol]}") # показать фактический рендер спорных сущностей for z in ["王胡", "假洋鬼子", "吴妈"]: can = ENT[z][0] frag = re.findall(r".{0,4}(?:Ван|Бород|заморск|поддельн|У-ма|У ма|Ху|Хо)\w*.{0,6}", out) print(f" {z}→канон «{can}»: фрагменты вывода {frag[:3]}") # РЫЧАГ: re-ask по реальным промахам miss = [z for z in PRESENT if z not in tol] if miss: terms = "; ".join(f"{z} = {ENT[z][0]}" for z in miss) reask = (GLOSS + "\n\nФРАГМЕНТ:\n" + CHUNK + f"\n\nВАЖНО: в переводе ОБЯЗАТЕЛЬНО используй утверждённые формы: {terms}. Перепиши перевод, применив их.") out2 = translate(reask) tol2 = score(out2, False) print(f" → post-check re-ask по {miss}: {len(tol)}/8 → {len(tol2)}/8 (толерантный)") else: print(" → промахов (толерантных) нет — re-ask не нужен") print() if __name__ == "__main__": main()