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