296 lines
12 KiB
Python
296 lines
12 KiB
Python
#!/usr/bin/env python3
|
||
"""$0 стройка проектов-проб для пакета «язык промптов (Р6) + fidelity-хвост P4».
|
||
|
||
Ничего не вызывает у провайдеров. Собирает под `~/books/gu-zhenren/promptlang/`:
|
||
|
||
A-ru / A-en — задача A: КОПИЯ БД холодного прогона (тот же book_id, тот же снапшот ⇒ черновые
|
||
чекпойнты попадают, волна не перекупается), различие ровно одно — `prompts_root`
|
||
пар-конфига указывает на дерево с ru- или en-терминологом. Полезная нагрузка роли
|
||
(кандидаты, KWIC, черновики, батчинг) при этом байт-идентична по построению:
|
||
она собирается из ОДНОЙ И ТОЙ ЖЕ БД одним и тем же кодом.
|
||
B-a0/a1/a2 — задача B: ЧИСТЫЕ БД на СВЕЖЕМ срезе (главы 11–16, вне среза холодного прогона),
|
||
одна черновая стадия, три варианта `translator.md` через `prompt_override`.
|
||
|
||
Стендовые артефакты не трогаются: всё живёт в отдельном каталоге, БД холодного прогона копируется.
|
||
"""
|
||
from __future__ import annotations
|
||
|
||
import shutil
|
||
import subprocess
|
||
import sys
|
||
from pathlib import Path
|
||
|
||
REPO = Path("/home/ubuntu/projects/textmachine")
|
||
BOOKS = Path.home() / "books" / "gu-zhenren"
|
||
PL = BOOKS / "promptlang"
|
||
COLD = BOOKS / "coldrun-a"
|
||
BACKEND = REPO / "backend"
|
||
|
||
SLICE_B = PL / "slice" / "guzhenren-s11-16.gb18030.txt"
|
||
|
||
PAIR_YAML = """# Пар-слой zh→ru — ПРОГОННАЯ КОПИЯ ратифицированного `backend/configs/pairs/zh-ru.yaml`.
|
||
# Отличие ровно одно и оно и есть предмет эксперимента: prompts_root показывает на дерево промптов
|
||
# арма. Числа нарезки и коридор len_ratio скопированы побайтно — иначе сравнивались бы две вещи.
|
||
pair: zh-ru
|
||
|
||
prompts_root: {prompts_root}
|
||
|
||
segmentation:
|
||
draft_budget_out: 1797
|
||
edit_ceiling_out: 3200
|
||
fertility:
|
||
cjk: 1.1978
|
||
other: 0.3852
|
||
|
||
coverage:
|
||
len_ratio_bounds: [2.2, 4.2]
|
||
"""
|
||
|
||
# ── задача A ────────────────────────────────────────────────────────────────────────────────────
|
||
A_PIPELINE = """# Задача A (Р6, «язык промптов»), арм {arm}. Ран-конфиг — КОПИЯ конфига холодного прогона.
|
||
# Изменены ровно два числа, и оба — предохранители, а не предмет замера:
|
||
# gates.terminology.budget_usd 0.05 → 0.07 (роль на этой БД уже потратила $0.030254; при 0.05
|
||
# арм оборвался бы на середине и сравнивать было бы
|
||
# нечего — усечённый арм это не результат, а брак)
|
||
# Всё, что входит в СНАПШОТ (стадии, модели, температуры, промпт черновика, гейты банкноты),
|
||
# оставлено побайтно — иначе черновая волна была бы перекуплена.
|
||
core: C1
|
||
version: 1
|
||
|
||
defaults:
|
||
max_output_ratio: 2.2
|
||
min_max_tokens: 2048
|
||
|
||
context:
|
||
glossary_injection: selective
|
||
glossary_token_budget: 800
|
||
cache_ttl: "5m"
|
||
|
||
waves:
|
||
workers: 4
|
||
|
||
retries:
|
||
regenerate_before_escalate: 1
|
||
|
||
stages:
|
||
- name: draft
|
||
role: translator
|
||
model: deepseek-v4-flash
|
||
prompt_override: /home/ubuntu/projects/textmachine/backend/prompts/zh-ru/translator-banknote.md
|
||
prompt_version: v1-reflow-banknote
|
||
temperature: 0.3
|
||
reasoning: "off"
|
||
escalate_to: deepseek-v4-pro
|
||
- name: edit
|
||
role: editor
|
||
model: deepseek-v4-pro
|
||
prompt_version: v3-discourse-reflow
|
||
temperature: 0.4
|
||
reasoning: "off"
|
||
few_shot: false
|
||
|
||
gates:
|
||
coverage:
|
||
enabled: false
|
||
sent_cov_min: 0.75
|
||
min_chunk_chars: 500
|
||
sanitizer:
|
||
enabled: true
|
||
regression_guard:
|
||
enabled: true
|
||
banknote:
|
||
enabled: true
|
||
terminology:
|
||
enabled: true
|
||
model: deepseek-v4-flash
|
||
budget_usd: 0.07
|
||
target_script: Cyrillic
|
||
voice:
|
||
enabled: true
|
||
|
||
escalation:
|
||
chains:
|
||
default: [deepseek-v4-pro, glm-5.1, gemini-3.1-pro-preview]
|
||
adult: [grok-4.3]
|
||
budget_usd: 0.10
|
||
|
||
fanout:
|
||
candidates: 1
|
||
|
||
mining:
|
||
contrast_path: /home/ubuntu/projects/textmachine/eval/exp16/data/jieba_dict_general_zh.txt
|
||
"""
|
||
|
||
A_BOOK = """# Задача A, арм {arm}. book_id СОХРАНЁН (`guzhenren-coldrun-a`): он входит в RequestHash
|
||
# (render.go:269-283), и его смена перекупила бы всю черновую волну. Срез, сид (его нет), langpack,
|
||
# models.yaml — те же файлы, что у холодного прогона; изменены только пути проекта и потолок.
|
||
book_id: guzhenren-coldrun-a
|
||
title: 蛊真人
|
||
source_lang: zh
|
||
target_lang: ru
|
||
genre: вебновелла
|
||
audience: взрослые читатели вебновелл
|
||
adult: false
|
||
venuti: 0.6
|
||
honorifics: keep
|
||
transcription: palladius
|
||
footnotes: minimal
|
||
|
||
pipeline: {dir}/pipeline.yaml
|
||
models: /home/ubuntu/projects/textmachine/backend/configs/models.yaml
|
||
source_file: /home/ubuntu/books/gu-zhenren/coldrun-a/guzhenren-ch1-10.gb18030.txt
|
||
encoding: gb18030
|
||
|
||
langpack_root: /home/ubuntu/projects/textmachine/backend/configs/langpacks
|
||
langpack_extend: /home/ubuntu/books/gu-zhenren/langpack-extend
|
||
|
||
mined_delta: {dir}/mined-delta.yaml
|
||
mined_rejects: {dir}/mined-rejects.yaml
|
||
|
||
project_db: {dir}/db.sqlite
|
||
|
||
# committed на копии = $0.126068 (наследство холодного прогона). Потолок 0.16 оставляет $0.0339 —
|
||
# больше ожидаемого прохода ($0.013), но физически способен выстрелить.
|
||
ceilings:
|
||
book_usd: 0.16
|
||
day_usd: 0.16
|
||
"""
|
||
|
||
# ── задача B ────────────────────────────────────────────────────────────────────────────────────
|
||
B_PIPELINE = """# Задача B (D21 п.6, анти-эхо), арм {arm}: {arm_note}
|
||
# ОДНА стадия — черновик. Редактуры нет: предмет замера — выход ЧЕРНОВОГО канала, а edit-волна
|
||
# стоила бы в четыре раза дороже всего пакета.
|
||
# escalate_to НЕТ и regenerate_before_escalate=0 СОЗНАТЕЛЬНО: эхо здесь ИЗМЕРЯЕТСЯ, а не чинится.
|
||
# Ремонт (эскалация на dspro) — боевое поведение, но он превратил бы один чанк в два-три вызова и
|
||
# смешал бы частоту эха с ценой его лечения.
|
||
core: C0
|
||
version: 1
|
||
|
||
defaults:
|
||
max_output_ratio: 2.2
|
||
min_max_tokens: 2048
|
||
|
||
context:
|
||
glossary_injection: selective
|
||
glossary_token_budget: 800
|
||
cache_ttl: "5m"
|
||
|
||
waves:
|
||
workers: 4
|
||
|
||
retries:
|
||
regenerate_before_escalate: 0
|
||
|
||
stages:
|
||
- name: draft
|
||
role: translator
|
||
model: deepseek-v4-flash
|
||
prompt_override: {arm_prompt}
|
||
prompt_version: promptlang-{arm}
|
||
temperature: 0.3
|
||
reasoning: "off"
|
||
|
||
gates:
|
||
coverage:
|
||
enabled: false
|
||
sent_cov_min: 0.75
|
||
min_chunk_chars: 500
|
||
sanitizer:
|
||
enabled: true
|
||
regression_guard:
|
||
enabled: true
|
||
banknote:
|
||
enabled: false
|
||
terminology:
|
||
enabled: false
|
||
voice:
|
||
enabled: false
|
||
|
||
fanout:
|
||
candidates: 1
|
||
"""
|
||
|
||
B_BOOK = """# Задача B, арм {arm}. Срез — главы 11–16 蛊真人 (строки 837..1293 utf8-копии), НЕ
|
||
# пересекается со срезом холодного прогона (главы 1–10): «свежая выборка» петли D21 п.6.
|
||
# Сида НЕТ: банк пуст, инъекция глоссария пуста во ВСЕХ трёх армах ⇒ инъекции байт-идентичны
|
||
# тривиально, и различие армов — ровно текст промпта.
|
||
book_id: promptlang-{arm}
|
||
title: 蛊真人
|
||
source_lang: zh
|
||
target_lang: ru
|
||
genre: вебновелла
|
||
audience: взрослые читатели вебновелл
|
||
adult: false
|
||
venuti: 0.6
|
||
honorifics: keep
|
||
transcription: palladius
|
||
footnotes: minimal
|
||
|
||
pipeline: {dir}/pipeline.yaml
|
||
models: /home/ubuntu/projects/textmachine/backend/configs/models.yaml
|
||
source_file: {slice}
|
||
encoding: gb18030
|
||
|
||
langpack_root: /home/ubuntu/projects/textmachine/backend/configs/langpacks
|
||
langpack_extend: /home/ubuntu/books/gu-zhenren/langpack-extend
|
||
|
||
project_db: {dir}/db.sqlite
|
||
|
||
ceilings:
|
||
book_usd: 0.02
|
||
day_usd: 0.02
|
||
"""
|
||
|
||
ARM_NOTES = {
|
||
"a0": "БОЕВОЙ translator.md побайтно (базлайн; sha совпадает с боевым файлом).",
|
||
"a1": "боевой + ПОСТИНСТРУКЦИЯ после {{text}} (хвост USER-части) — дословно строка P4.",
|
||
"a2": "боевой + МИКРО-FEW-SHOT в штатный блок ---FEWSHOT--- (хвост system, D38.4) — дословно пары P4.",
|
||
}
|
||
|
||
|
||
def write(p: Path, s: str) -> None:
|
||
p.parent.mkdir(parents=True, exist_ok=True)
|
||
p.write_text(s, encoding="utf-8")
|
||
|
||
|
||
def build_a() -> None:
|
||
for arm, root in (("ru", PL / "prompts-ru"), ("en", PL / "prompts-en")):
|
||
d = PL / f"A-{arm}"
|
||
d.mkdir(parents=True, exist_ok=True)
|
||
shutil.copy2(COLD / "guzhenren-coldrun-a.db", d / "db.sqlite")
|
||
shutil.copy2(COLD / "mined-delta.yaml", d / "mined-delta.yaml")
|
||
shutil.copy2(COLD / "mined-rejects.yaml", d / "mined-rejects.yaml")
|
||
write(d / "pipeline.yaml", A_PIPELINE.format(arm=arm))
|
||
write(d / "book.yaml", A_BOOK.format(arm=arm, dir=d))
|
||
write(d / "pairs" / "zh-ru.yaml", PAIR_YAML.format(prompts_root=root))
|
||
print(f"A-{arm}: собран, prompts_root={root}")
|
||
|
||
|
||
def build_b() -> None:
|
||
for arm in ("a0", "a1", "a2"):
|
||
d = PL / f"B-{arm}"
|
||
d.mkdir(parents=True, exist_ok=True)
|
||
for stale in ("db.sqlite", "db.sqlite.lock"):
|
||
(d / stale).unlink(missing_ok=True)
|
||
write(d / "pipeline.yaml", B_PIPELINE.format(
|
||
arm=arm, arm_note=ARM_NOTES[arm], arm_prompt=PL / "arms" / f"translator-{arm}.md"))
|
||
write(d / "book.yaml", B_BOOK.format(arm=arm, dir=d, slice=SLICE_B))
|
||
write(d / "pairs" / "zh-ru.yaml", PAIR_YAML.format(prompts_root=BACKEND / "prompts"))
|
||
print(f"B-{arm}: собран, промпт={PL / 'arms' / f'translator-{arm}.md'}")
|
||
|
||
|
||
def main() -> int:
|
||
if not SLICE_B.exists():
|
||
print(f"нет среза {SLICE_B}", file=sys.stderr)
|
||
return 2
|
||
build_a()
|
||
build_b()
|
||
# Байт-парити ru-дерева промптов с боевым (арм A обязан быть РЕПЛИКОЙ, а не пересказом).
|
||
r = subprocess.run(["diff", "-r", str(BACKEND / "prompts"), str(PL / "prompts-ru")],
|
||
capture_output=True, text=True)
|
||
print("\nprompts-ru vs backend/prompts:", "БАЙТ-ИДЕНТИЧНЫ" if r.returncode == 0 else r.stdout[:500])
|
||
return 0
|
||
|
||
|
||
if __name__ == "__main__":
|
||
raise SystemExit(main())
|