textmachine/eval/adaptive_reask.py

86 lines
5.6 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/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()