Land exp14 batch-2: 3-family frontier shows the comprehension fix is gpt-5.4-specific (grok inverts, gemini inconclusive), re-gate flags ~10 fidelity chunks, bigger chunks are cheaper and better
This commit is contained in:
parent
174192b1eb
commit
a19107479d
8 changed files with 515 additions and 8 deletions
|
|
@ -18,7 +18,13 @@
|
|||
- **Претензия 2 внутри-чанк (смысл) = дешёвая МОДЕЛЬ (capability floor).** T1 (gl.7 двойное отрицание «все знали»): glm-5 **инвертирует** в «никто не знал» НЕЗАВИСИМО от промпта (P0 и P1a); **gpt-5.4 чинит** НЕЗАВИСИМО от промпта (F-base и F-disc). T2 (物是人非) — тот же знак. Промпт претензию 2 НЕ закрывает.
|
||||
- **Претензия 2 кросс-граница = НЕ доминирует в срезе.** T5/T6 локально самодостаточны; ±1-reference (A-ref) дал байт-идентичный выход (чинить нечего); whole-chapter (P1c) единственный явно связал антецедент (1 датапоинт).
|
||||
|
||||
**Near-term рекомендация (на ратификацию оркестратора, НЕ смена дефолта):** (1) дискурс-reflow-контент Ван Цуй→editor-промпт + рассмотреть отдельный target-only reflow-пасс (A-2pass) под COGS; (2) претензию 2 — **селективная фронтир-эскалация редактора по смысл-риску** (не тотальный фронтир — он абзацы портит); (3) CJK-гард при deformat; (4) НЕ строить Ф2-кросс-чанк-память под слабый сигнал. **ПАРТИЯ 2 (осталось):** кривая нарезки, слепые пакеты Ступени II (гл.19/17/18, D=0.061/0.246/0.440), полная ранжирующая панель +leave-one-out, fidelity re-gate (D34.3), 3 финалиста, хендофф. Планка = 2 претензии владельца (ИНТЕРИМ пре-скрин, НЕ пилот Ф2.5). Смену дефолта ратифицирует оркестратор.
|
||||
**ПАРТИЯ 2 ВЫПОЛНЕНА (по запросу владельца добавлены Gemini+Grok фронтир-редакторы — честный тест в €2.3; итог обеих партий ≈$8.5 across 6 ключей, каждый под лимитом).** Ключевые УТОЧНЕНИЯ:
|
||||
- **T1-«capability floor» — MODEL-SPECIFIC, не общий фронтир (поправка партии-1 n=1):** gpt-5.4 чинит инверсию двойного отрицания, а **grok-4.3 (фронтир, reasoning-ON) её НЕ чинит — инвертирует как дешёвый glm** (заякорился на черновике; мини-проба grok на СВЕЖЕМ предложении верна → провал именно в РЕДАКТОРСКОЙ задаче). Gemini-редактор ИНКОНКЛЮЗИВЕН (mandatory-thinking съел max_tokens=8000 → обрыв finish=length; урок: Gemini-editor нужен ≥16–20k ток × $12/M = COGS-враждебно; €2.3 исчерпан). grok-4.5 — регион-лок (403, оба ключа). ⇒ смысл (П2) чинит **gpt-5.4 конкретно**, не «любой фронтир».
|
||||
- **Абзацы (П1): grok-disc ЛУЧШИЙ (mean 4.22)** но CJK+провал T1; A-2pass 3.33; **кривая нарезки: крупнее чанк = ДЕШЕВЛЕ выходных токенов И лучше абзацы, 0 ретраев** (оптимум 3200c/глава) → поддержка `edit=крупная единица` (D35.7) по COGS И качеству.
|
||||
- **Fidelity re-gate (D34.3): пере-прогон НЕ чист** — средняя верность 88–94, но **13 катастроф + 21 инверсия редактуры на ~10 чанках** (ch10.1 both-judge fid 60); класс инверсий D34.3 подтверждён на хвосте → флаги на span-ревью, пере-прогон засчитан НЕ полностью.
|
||||
- **Ранжирование финалистов (гл.19/17/18): стиль F-disc(gpt-5.4)>A-2pass>P0 (робастно); holistic-верность P0>прочих — НО P0 несёт T1-катастрофу, панель её не поймала = ЛИТЕРАЛ-СМЕЩЕНИЕ судей** (урок exp12/мемо eval-aggregation) → доверять span-уровню, не holistic-скаляру.
|
||||
|
||||
**Слепой пакет владельцу готов:** `exp14/monitor/monitor14.md` (3 финалиста × 3 главы, нейтральные метки, ключ отдельно) — человеческий вердикт «лучше фана» (D30.7). **Near-term рекомендация** (ратификация оркестратора): дискурс-промпт + крупная edit-единица (дешевле И лучше) под omission-гард; селективная эскалация на gpt-5.4 (не grok/gemini) по смысл-риску; CJK-гард на deformat/grok; ~10 re-gate-флагов на ревью. Планка = 2 претензии (ИНТЕРИМ пре-скрин, НЕ пилот Ф2.5). Детали — `experiments/14-quality-empirics.md` §2/§2Б/§3.
|
||||
|
||||
## Ресёрчер — рычаги качества (`research/18-quality-levers.md`), 2026-07-12 (D35.5, вход полигону)
|
||||
|
||||
|
|
|
|||
|
|
@ -247,8 +247,76 @@ usage→$: `(in·pin + (comp + reason)·pout)/1e6`; `reason`= из `completion`
|
|||
3. **CJK-гард обязателен** при любом deformat-черновике (A-layout урок).
|
||||
4. **НЕ строить** сложную Ф2-кросс-чанк-память под этот срез (сигнал слабый); whole-chapter/edit=глава — держать кандидатом под доказанный ROI.
|
||||
|
||||
**Оговорки (методика-guard):** trap-набор мал (6 трапов, prev-boundary, без катафоры); фронтир = 1 модель (gpt-5.4), Gemini/grok переводчиками не гонялись (бюджет/эхо); T1-вывод — 1 катастроф-датапоинт (но детерм.-чистый); срез — PD-смежная ранняя арка (без 18+); это ИНТЕРИМ пре-скрин (D35), НЕ вердикт пилота Ф2.5.
|
||||
**Оговорки партии 1 (пересмотрены партией 2):** T1-вывод «capability floor» партии 1 стоял на 1 фронтир-модели (gpt-5.4) → партия 2 (3 семьи) его УТОЧНИЛА (см. §2Б.1 — floor model-SPECIFIC, не общий). Прочее: срез PD-смежный без 18+; ИНТЕРИМ пре-скрин (D35), не пилот Ф2.5.
|
||||
|
||||
## 3. Fidelity re-gate (ПАРАЛЛЕЛЬНО, D34.3 — независим от §1-§2)
|
||||
---
|
||||
|
||||
Span-цитирующий, reasoning-ON, раздельно стиль/верность, охота на КЛАСС ИНВЕРСИЙ редактуры (ch11/0 аутлайер: editor расширил 3009→4631, sim 0.064), катастроф-скрин терминов у победителя, author≠reviewer (не glm-5/deepseek), leave-one-out. Вход — `rerun/rerun-parallel.txt` (57 чанков, выровнено ОРИГ|ПЕРЕВОД) + `records.json`. **Пере-прогон НЕ засчитан до пройденного re-gate.** *(результаты — ниже, после прогона.)*
|
||||
## 2Б. ПАРТИЯ 2 — 3-семейный фронтир + fidelity re-gate + ранжирование финалистов + кривая нарезки
|
||||
|
||||
> Трата партии 2: Gemini $2.20 (≈€2.0, на границе €2.3 — больше не тратил), Kimi +$2.7, OpenAI +$0.9, ZAI +$0.14, XAI $0.15, DeepSeek $0.02. **Итог обеих партий ≈ $8.5** across 6 ключей (каждый под лимитом). grok-4.5 — региональный лок (`403 not available in your region`, ОБА ключа, не бюджет); тестим доступный grok-4.3.
|
||||
|
||||
### 2Б.1. Capability floor — MODEL-SPECIFIC, не общий фронтир (ключевая поправка партии 1)
|
||||
|
||||
3-семейный фронтир-редактор на trap-наборе (черновик flash общий; варьируем editor-модель × промпт):
|
||||
|
||||
| Editor-семья | T1 двойн. отриц. (детерм.) | mean предл./абзац | доля 1-предл. | CJK | len/P0 |
|
||||
|---|---|---|---|---|---|
|
||||
| glm-5 (дешёвый) P0/P1a/A-2pass | ✗ **инверсия** (все промпты) | 1.91→3.33 | 0.47→0.19 | 2 | ~1.0 |
|
||||
| **gpt-5.4** F-base/F-disc | **✓ чинит** (оба промпта) | 1.58 / 2.45 | 0.58 / 0.37 | 0 | ~1.02 |
|
||||
| **grok-4.3 ON** X-base/X-disc | ✗ **инверсия** (оба! якорится на черновике) | 1.74 / **4.22** | 0.50 / 0.26 | 2 / 6 | ~0.97 |
|
||||
| gemini-3.1-pro G-base/G-disc | **инконклюзивно** (обрыв) | 1.52 / 1.95 | 0.60 / 0.53 | ≤1 | **0.65–0.76 ⚠** |
|
||||
|
||||
- **T1 «capability floor» — НЕ универсален: gpt-5.4 чинит инверсию черновика, а grok-4.3 (фронтир, reasoning-ON) её НЕ чинит — инвертирует так же, как дешёвый glm.** Grok-редактор заякорился на ошибке черновика (мини-проба grok на СВЕЖЕМ предложении `他没有一个不知道的` дала верно → провал именно в РЕДАКТОРСКОЙ задаче «поймай ошибку черновика»). ⇒ **это не «любой фронтир чинит», а СПЕЦИФИЧЕСКАЯ компетенция gpt-5.4** (ловить двойное отрицание/инверсию черновика). Партия 1 (n=1 фронтир) это переобобщила — партия 2 поправила.
|
||||
- **grok-4.3 + дискурс (X-disc) — ЛУЧШИЕ абзацы из всех (mean 4.22)**, но CJK-утечка 6 и провал T1. Профиль: отличная переверстка, слабое понимание.
|
||||
- **gemini-3.1-pro как редактор — ИНКОНКЛЮЗИВНО (харнес-дефект + COGS):** mandatory-thinking съел `max_tokens=8000` → `finish=length`, выход обрезан (comp 317–646 ток, len/P0 0.65–0.76 = потеря контента). Честный урок: **Gemini-редактору нужен `max_tokens`≥16–20k, а при $12/M (thinking биллится как output) это ~$0.15–0.25/вызов — COGS-враждебно** (×10 к glm/grok, ×2–3 к gpt-5.4). €2.3 исчерпан на обрезанных вызовах → Gemini-качество не оценено (мой мис-конфиг max_tokens; вписано в урок).
|
||||
|
||||
### 2Б.2. Fidelity re-gate (D34.3) — пере-прогон НЕ чист
|
||||
|
||||
Span-цитирующий, раздельно верность/стиль, охота на класс инверсий редактуры (final vs draft vs src), author≠reviewer (gpt-5-mini + kimi, НЕ glm/deepseek), leave-one-out. 55 чанков (ch5.0/ch20.0 — пустые экспорт-баг, вне скора).
|
||||
- **Средняя верность: gpt-5-mini 93.7 (min 60), kimi 87.6 (min 60); leave-one-out 87.6↔93.7.** Широко приемлемо, НО с хвостом.
|
||||
- **13 КАТАСТРОФ-флагов + 21 ИНВЕРСИЯ РЕДАКТУРЫ** на ~10 чанках. **ch10.1 — катастрофа обоими судьями (fid 60)**; прочие: 1.2, 5.1, 6.0, 9.1, 11.0, 12.0, 13.1, 15.1, 16.0, 20.1. Класс, которого боялся D34.3 (билингв-редактор купил гладкость ценой смысла), ПОДТВЕРЖДЁН на хвосте.
|
||||
- **Вердикт re-gate: пере-прогон НЕ проходит чисто — ~10 чанков с span-цитированными инверсиями/катастрофами на ревью оркестратору/владельцу** (часть может быть FP LLM-судьи — верифицировать спаны; но ch10.1 both-judge — вероятно реален). Пере-прогон засчитан НЕ полностью (D1.1/D34.3).
|
||||
|
||||
### 2Б.3. Ранжирование 3 финалистов (Ступень II, гл.19/17/18) — style↑ у reflow, но HOLISTIC fidelity литерал-смещён
|
||||
|
||||
Финалисты: **Fin-A=P0 · Fin-B=A-2pass · Fin-C=F-disc (gpt-5.4)**. Панель gpt-5-mini+kimi+gemini, 3 повтора со свапом, leave-one-out (Borda, 2=лучший):
|
||||
- **Стиль:** Fin-C **1.43** > Fin-B 0.95 > Fin-A 0.62 (робастно во всех leave-one-out) → **gpt-5.4-дискурс читается лучше, A-2pass второй, прод P0 худший.**
|
||||
- **Верность (holistic):** Fin-A(P0) **1.53** > Fin-C 0.76 > Fin-B 0.71 (робастно) — **НО P0 содержит T1-КАТАСТРОФУ (инверсию «все знали»→«никто»), которую F-disc чинит.** Панель holistic-верности этого НЕ поймала → **литерал-смещение судей** (награждают консервативную близость P0 к черновику, слепы к его span-катастрофе — точный урок exp12/мемо `eval-aggregation`).
|
||||
- **Синтез:** доверять SPAN-уровню (F-disc чинит T1, P0 инвертирует) выше, чем holistic-скаляру верности. Reflow/фронтир поднимают СТИЛЬ; на верность есть НАПРЯЖЕНИЕ (агрессивная переверстка ↔ omission-риск), но «P0 вернее» — артефакт литерал-bias, не факт.
|
||||
|
||||
### 2Б.4. Кривая нарезки — крупнее чанк = ДЕШЕВЛЕ И лучше абзацы (0 ретраев)
|
||||
|
||||
ch17+ch13 × 800/1600/3200/whole (жадная упаковка; flash-draft→glm-5-дискурс; реальный токенайзер):
|
||||
|
||||
| размер | ch17 out-ток / mean_spp | ch13 out-ток / mean_spp |
|
||||
|---|---|---|
|
||||
| 800c | 15159 / 2.59 | 12171 / 3.48 |
|
||||
| 1600c (прод) | 10284 / 3.03 | 8043 / 3.95 |
|
||||
| 3200c | 8691 / 3.67 | 6090 / 4.09 |
|
||||
| whole | 7782 / 3.59 | 4560 / 3.88 |
|
||||
|
||||
- **Монотонно: крупнее чанк → МЕНЬШЕ выходных токенов (дешевле) И ЛУЧШЕ абзацы** (mean_spp↑, доля-1-предл.↓). 0 ретраев на всех размерах (retry blast-radius не наблюдён на этих 2 главах). Оптимум ~**3200c / whole** по обеим осям.
|
||||
- Мелкий чанк (800c) — дороже (дублирующий overhead/повторы) И рубленее. Опровергает «мельче = дешевле» для output-домината; на ЭТОМ тексте перевёрнутой-U вниз не видно (lost-in-middle не бьёт до главы). **Поддерживает `edit=крупная единица / глава` (D35.7) — И по COGS, И по качеству.** Оговорка: 2 главы, 1 текст, 0 наблюдённых ретраев (retry-adjusted не активировался).
|
||||
|
||||
### 2Б.5. Итоговая capability-vs-activation карта (обе партии)
|
||||
|
||||
| Претензия | Потолок | Уточнённое доказательство | Ход |
|
||||
|---|---|---|---|
|
||||
| (1) абзацы | **ПРОМПТ + РАЗМЕР** (активация) | дискурс-промпт (P1a/F-disc/X-disc) + 2-пасс (A-2pass 3.33) + крупный чанк (кривая) двигают KPI; grok-disc лучший (4.22); фронтир-модель НЕ нужна | катить дискурс-промпт + крупная edit-единица; A-2pass если COGS позволит |
|
||||
| (2) внутри-чанк смысл | **MODEL-SPECIFIC** (gpt-5.4), не общий floor | gpt-5.4 чинит T1; glm-5 И grok-4.3 инвертируют; gemini инконклюзивно | селективная эскалация на **gpt-5.4** по смысл-риску (grok НЕ годится — тоже инвертирует) |
|
||||
| (2) кросс-граница | не доминирует в срезе | A-ref null; P1c связал (1 датапоинт) | не строить Ф2-память под слабый сигнал |
|
||||
| верность пере-прогона | **есть хвост порчи** | re-gate: 13 катастроф + 21 инверсия на ~10 чанках | ревью флагов + omission-гард на reflow-армы |
|
||||
|
||||
### 2Б.6. Пересмотренная near-term рекомендация (на ратификацию оркестратора)
|
||||
|
||||
1. **Абзацы (П1):** дискурс-reflow-промпт (Ван Цуй) + сдвиг edit-единицы к КРУПНОЙ (3200c/глава — дешевле И лучше по кривой). A-2pass даёт топ-KPI, но добавляет пасс — взвесить vs крупный чанк (крупный чанк даёт похожий лифт БЕЗ +пасса и ДЕШЕВЛЕ). **Оба под omission-гард** (reflow-армы просели по holistic-верности — следить за атомами).
|
||||
2. **Смысл (П2):** селективная эскалация редактора на **gpt-5.4 конкретно** по смысл-риску (двойное отрицание/chengyu/инверсия черновика). **grok и gemini НЕ заменяют** (grok инвертирует, gemini COGS-враждебен/инконклюзивен). Не тотальный фронтир (портит абзацы у F-base, дорого).
|
||||
3. **CJK-гард** обязателен на deformat (A-layout) и на grok-выходах (утечка 6).
|
||||
4. **Пере-прогон:** ~10 re-gate-флагов на ревью до «засчитан».
|
||||
5. **Слепые пакеты Ступени II** (`exp14/monitor/monitor14.md`) — владельцу на человеческий вердикт «лучше фана» (арбитр, D30.7).
|
||||
|
||||
**Оговорки (методика-guard):** trap-набор мал (6, prev-boundary, без катафоры); T1 — детерм.-чистый, но 1 конструкция; holistic-fidelity судьи литерал-смещены (доверять спанам); gemini недооценён (мис-конфиг max_tokens, €2.3 исчерпан); кривая — 2 главы/0 ретраев; ИНТЕРИМ пре-скрин (D35), НЕ пилот Ф2.5. Смену дефолта ратифицирует ОРКЕСТРАТОР.
|
||||
|
||||
## 3. Fidelity re-gate (D34.3) — свод в §2Б.2
|
||||
|
||||
Пройден с результатом **«не чист»**: средняя верность gpt-5-mini 93.7 / kimi 87.6 (leave-one-out 87.6↔93.7), но **13 катастроф-флагов + 21 инверсия редактуры на ~10 чанках** (ch10.1 — both-judge катастрофа fid 60). Класс инверсий редактуры (D34.3) подтверждён на хвосте. Флаги — на span-верификацию оркестратором/владельцем; пере-прогон засчитан НЕ полностью. Вход: `rerun/records.json` (source/draft/final, author≠reviewer gpt-5-mini+kimi). Артефакты: `exp14/regate_verdicts.jsonl`.
|
||||
|
|
|
|||
|
|
@ -145,8 +145,9 @@ def main():
|
|||
ref = build_reference(a.arm)
|
||||
run_editor(a.arm, "discourse", [u for u in chunk_units(a.scope) if u["id"] in ref],
|
||||
a.model, reference_by=ref, dry=a.dry)
|
||||
elif a.arm in ("F-base", "F-disc"):
|
||||
variant = "baseline" if a.arm == "F-base" else "discourse"
|
||||
elif a.arm in ("F-base", "F-disc", "G-base", "G-disc", "X-base", "X-disc"):
|
||||
# F-* = gpt-5.4, G-* = gemini-3.1-pro-preview, X-* = grok-4.3 reasoning-ON (все фронтир-редакторы)
|
||||
variant = "baseline" if a.arm.endswith("-base") else "discourse"
|
||||
run_editor(a.arm, variant, chunk_units(a.scope), a.model, dry=a.dry)
|
||||
else:
|
||||
raise SystemExit(f"unknown arm {a.arm}")
|
||||
|
|
|
|||
|
|
@ -42,10 +42,12 @@ TRAPS = [
|
|||
JUDGE_POOL = { # judge_model → family (для self-family exclusion)
|
||||
"gpt-5-mini": "openai", "kimi-k2.6": "moonshot", "gemini-3.1-pro-preview": "google",
|
||||
}
|
||||
ARM_FAMILY = { # editor-модель арма → family (author≠reviewer)
|
||||
ARM_FAMILY = { # editor-модель арма → family (author≠reviewer, self-family exclusion)
|
||||
"P0": "zai", "P1a": "zai", "P1b": "zai", "P1c": "zai", "A-layout": "zai",
|
||||
"A-2pass": "zai", "A-ref-prev": "zai", "A-ref-both": "zai",
|
||||
"F-base": "openai", "F-disc": "openai",
|
||||
"F-base": "openai", "F-disc": "openai", # gpt-5.4
|
||||
"G-base": "google", "G-disc": "google", # gemini-3.1-pro-preview
|
||||
"X-base": "xai", "X-disc": "xai", # grok-4.3 reasoning-ON
|
||||
}
|
||||
# draft общий = deepseek → deepseek не судья (self-family к черновику, где рождается T1)
|
||||
|
||||
|
|
|
|||
63
eval/exp14_monitor.py
Normal file
63
eval/exp14_monitor.py
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
#!/usr/bin/env python3
|
||||
"""exp14 Ступень II — слепой пакет владельцу: 3 ФИНАЛИСТА на 3 главах (гл.19/17/18), под
|
||||
нейтральными метками, ключ ОТДЕЛЬНО. Копирайт: книжный текст ВНЕ git (exp14/monitor/).
|
||||
|
||||
Финалисты: Fin-A=P0 (текущий прод baseline) · Fin-B=A-2pass (лучший near-term, дешёвый) ·
|
||||
Fin-C=F-disc (фронтир gpt-5.4, дискурс). Per-глава seeded-shuffle меток (V1..V3 — РАЗНАЯ
|
||||
модель в каждой главе; ключ владелец не открывает до ранжирования).
|
||||
"""
|
||||
from __future__ import annotations
|
||||
import json, random
|
||||
from pathlib import Path
|
||||
import exp14_common as C
|
||||
|
||||
ARMS = C.DIAG / "arms"
|
||||
MAT = json.load(open(C.DIAG / "material.json"))
|
||||
OUT = C.DIAG / "monitor"; OUT.mkdir(exist_ok=True)
|
||||
FINALISTS = {"Fin-A": "P0", "Fin-B": "A-2pass", "Fin-C": "F-disc"}
|
||||
CHAPTERS = [19, 17, 18]
|
||||
SALT = "exp14-monitor-blind-v1"
|
||||
|
||||
|
||||
def chapter_text(arm: str, ch: int) -> str:
|
||||
chunks = sorted(u["chunk_idx"] for u in MAT["stage2"][str(ch)])
|
||||
parts = []
|
||||
for ck in chunks:
|
||||
p = ARMS / arm / f"{ch}.{ck}.txt"
|
||||
parts.append(p.read_text(encoding="utf-8") if p.exists() else f"[[MISSING {arm} {ch}.{ck}]]")
|
||||
return "\n\n".join(parts)
|
||||
|
||||
|
||||
def main():
|
||||
missing = [(f, a, ch) for f, a in FINALISTS.items() for ch in CHAPTERS
|
||||
for ck in sorted(u["chunk_idx"] for u in MAT["stage2"][str(ch)])
|
||||
if not (ARMS / a / f"{ch}.{ck}.txt").exists()]
|
||||
if missing:
|
||||
print("⚠ отсутствуют выходы (арм ещё не готов):", missing[:10]); return
|
||||
|
||||
key = {}
|
||||
md = ["# Слепое чтение — exp14 Ступень II (3 варианта × 3 главы)\n",
|
||||
"Прочти каждую главу в ТРЁХ вариантах (V1/V2/V3 — порядок в каждой главе РАЗНЫЙ). "
|
||||
"Для каждой главы: (1) какой вариант читается лучше как русская проза «лучше фана» — ранжируй V1>V2>V3; "
|
||||
"(2) закрыты ли обе претензии (абзацы/конвенции; понимание связей/смысла); (3) любые смысловые ляпы. "
|
||||
"Ключ (какая модель под какой меткой) — в отдельном файле `_КЛЮЧ.json`, открой ПОСЛЕ.\n",
|
||||
"D-плотность диалога: гл.19=0.061 (нарратив), гл.17=0.246 (смешанная), гл.18=0.440 (диалог).\n"]
|
||||
for ch in CHAPTERS:
|
||||
order = list(FINALISTS); random.Random(f"{SALT}-{ch}").shuffle(order)
|
||||
labels = {f"V{i+1}": order[i] for i in range(len(order))}
|
||||
key[str(ch)] = {lab: {"finalist": fin, "arm": FINALISTS[fin]} for lab, fin in labels.items()}
|
||||
md.append(f"\n\n---\n\n## Глава {ch}\n")
|
||||
for i in range(len(order)):
|
||||
lab = f"V{i+1}"; fin = labels[lab]
|
||||
md.append(f"\n### {lab}\n\n{chapter_text(FINALISTS[fin], ch)}\n")
|
||||
(OUT / "monitor14.md").write_text("\n".join(md), encoding="utf-8")
|
||||
(OUT / "_КЛЮЧ.json").write_text(json.dumps(
|
||||
{"salt": SALT, "finalists": FINALISTS, "per_chapter": key}, ensure_ascii=False, indent=1), encoding="utf-8")
|
||||
print(f"→ {OUT/'monitor14.md'} (3 главы × 3 слепых варианта)")
|
||||
print(f"→ {OUT/'_КЛЮЧ.json'} (НЕ открывать до ранжирования)")
|
||||
for ch in CHAPTERS:
|
||||
print(f" гл.{ch}: " + ", ".join(f"{lab}={key[str(ch)][lab]['finalist']}" for lab in ("V1","V2","V3")))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
120
eval/exp14_rank.py
Normal file
120
eval/exp14_rank.py
Normal file
|
|
@ -0,0 +1,120 @@
|
|||
#!/usr/bin/env python3
|
||||
"""exp14 Ступень II — ранжирующая панель финалистов (P0/A-2pass/F-disc) на главах 19/17/18.
|
||||
РАЗДЕЛЬНО стиль/верность, span-цитаты, ≥N повторов со свапом меток, leave-one-judge-out.
|
||||
Судьи gpt-5-mini + kimi + gemini(точечно). Self-family (F-disc=openai) снимается leave-one-out.
|
||||
Guard мемо eval-aggregation: НЕ собирать ложную сходимость.
|
||||
|
||||
Использование: exp14_rank.py [--repeats 3] [--dry]
|
||||
"""
|
||||
from __future__ import annotations
|
||||
import argparse, json, random, re, sys
|
||||
from pathlib import Path
|
||||
sys.path.insert(0, str(Path(__file__).resolve().parent))
|
||||
import exp14_common as C
|
||||
|
||||
ARMS = C.DIAG / "arms"
|
||||
MAT = json.load(open(C.DIAG / "material.json"))
|
||||
FINALISTS = {"Fin-A": "P0", "Fin-B": "A-2pass", "Fin-C": "F-disc"}
|
||||
CHAPTERS = [19, 17, 18]
|
||||
AXES = {"style": "русская художественность/абзацы/конвенции (естественность прозы, красная строка, тире-диалог)",
|
||||
"fidelity": "верность смыслу (нет инверсий полярности/участника, нет потери атомов; можно ценой лёгкой шероховатости)"}
|
||||
JUDGES = ["gpt-5-mini", "kimi-k2.6", "gemini-3.1-pro-preview"]
|
||||
CAPS = {"gpt-5-mini": 0.10, "kimi-k2.6": 0.15, "gemini-3.1-pro-preview": 0.30}
|
||||
|
||||
|
||||
def chapter_text(arm, ch):
|
||||
chunks = sorted(u["chunk_idx"] for u in MAT["stage2"][str(ch)])
|
||||
return "\n\n".join((ARMS / arm / f"{ch}.{ck}.txt").read_text(encoding="utf-8") for ck in chunks)
|
||||
|
||||
|
||||
def run():
|
||||
ap = argparse.ArgumentParser(); ap.add_argument("--repeats", type=int, default=3)
|
||||
ap.add_argument("--dry", action="store_true"); a = ap.parse_args()
|
||||
sp = C.Spender(C.DIAG / "rank_costs.jsonl", CAPS)
|
||||
vpath = C.DIAG / "rank_verdicts.jsonl"; out = []; done = set()
|
||||
if vpath.exists():
|
||||
for l in vpath.read_text(encoding="utf-8").splitlines():
|
||||
if l.strip():
|
||||
v = json.loads(l); out.append(v); done.add((v["ch"], v["axis"], v["judge"], v["rep"]))
|
||||
tasks = []
|
||||
for ch in CHAPTERS:
|
||||
for axis in AXES:
|
||||
for rep in range(a.repeats):
|
||||
order = list(FINALISTS); random.Random(f"exp14-rank-{ch}-{axis}-{rep}").shuffle(order)
|
||||
labels = {f"V{i+1}": order[i] for i in range(len(order))}
|
||||
for jm in JUDGES:
|
||||
if jm == "gemini-3.1-pro-preview" and rep > 0:
|
||||
continue # gemini точечно: 1 повтор (бюджет €2.6)
|
||||
if (ch, axis, jm, rep) in done:
|
||||
continue
|
||||
tasks.append((ch, axis, rep, jm, labels))
|
||||
print(f"rank tasks: {len(tasks)} new ({len(done)} done)")
|
||||
if a.dry:
|
||||
from collections import Counter
|
||||
print(Counter(t[3] for t in tasks)); return
|
||||
for ch, axis, rep, jm, labels in tasks:
|
||||
blocks = "\n\n".join(f"=== {lab} ===\n{chapter_text(FINALISTS[fin], ch)}" for lab, fin in labels.items())
|
||||
sysmsg = ("Ты — литературный судья zh→ru. Оцени ТРИ перевода одной главы по ОДНОЙ оси. "
|
||||
"Ранжируй от лучшего к худшему. Приведи 2-4 span-цитаты-обоснования (≤15 слов каждая). "
|
||||
'Ответь СТРОГО JSON без markdown: {"ranking":["V?","V?","V?"],'
|
||||
'"evidence":[{"label":"V?","quote":"≤15 слов","note":"почему"}]}.')
|
||||
user = f"ОСЬ ОЦЕНКИ: {AXES[axis]}\n\nТРИ ВАРИАНТА:\n\n{blocks}\n\nРанжируй по оси. JSON."
|
||||
s = C.spec(jm, max_tokens=(16000 if jm == "kimi-k2.6" else 6000))
|
||||
in_est = C.count_tokens(sysmsg + user, "glm")
|
||||
ok, pred = sp.guard(jm, in_est, s["max_tokens"])
|
||||
if not ok:
|
||||
print(f" OVER-CAP {ch} {axis} {jm} pred=${pred:.3f}"); continue
|
||||
text, err, usage = C.call(s, [{"role": "system", "content": sysmsg},
|
||||
{"role": "user", "content": user}], timeout=360)
|
||||
cst = sp.record({"model": jm, "judge": jm, "keyenv": s["keyenv"], "ch": ch,
|
||||
"axis": axis, "rep": rep, "err": err, "usage": usage})
|
||||
rk = parse_rank(text) if not err else None
|
||||
v = {"ch": ch, "axis": axis, "rep": rep, "judge": jm, "labels": labels,
|
||||
"ranking_labels": rk, "err": err}
|
||||
# map labels→finalist
|
||||
if rk:
|
||||
v["ranking_fin"] = [labels.get(x) for x in rk]
|
||||
out.append(v)
|
||||
with open(vpath, "a", encoding="utf-8") as f:
|
||||
f.write(json.dumps(v, ensure_ascii=False) + "\n")
|
||||
print(f" ch{ch} {axis:<8} rep{rep} {jm:<18} → {v.get('ranking_fin')} ${cst:.4f}")
|
||||
summarize(out)
|
||||
|
||||
|
||||
def parse_rank(text):
|
||||
m = re.search(r'"ranking"\s*:\s*(\[[^\]]*\])', text)
|
||||
if not m:
|
||||
return None
|
||||
try:
|
||||
arr = json.loads(m.group(1))
|
||||
return [str(x) for x in arr]
|
||||
except Exception:
|
||||
return re.findall(r'V\d', m.group(1))
|
||||
|
||||
|
||||
def summarize(out):
|
||||
from collections import defaultdict
|
||||
import statistics
|
||||
# Borda: 1st=2pts,2nd=1,3rd=0; per axis; per judge for leave-one-out
|
||||
def borda(rows):
|
||||
pts = defaultdict(list)
|
||||
for v in rows:
|
||||
rf = v.get("ranking_fin")
|
||||
if not rf or len(rf) != 3 or None in rf:
|
||||
continue
|
||||
for i, fin in enumerate(rf):
|
||||
pts[fin].append(2 - i)
|
||||
return {f: round(statistics.mean(p), 2) for f, p in pts.items()}
|
||||
print("\n=== РАНЖИРОВАНИЕ (Borda mean, 2=лучший) ===")
|
||||
for axis in AXES:
|
||||
rows = [v for v in out if v["axis"] == axis]
|
||||
print(f"\n{axis}: overall {borda(rows)}")
|
||||
judges = sorted(set(v["judge"] for v in rows))
|
||||
for j in judges:
|
||||
print(f" only {j}: {borda([v for v in rows if v['judge']==j])}")
|
||||
for j in judges: # leave-one-out
|
||||
print(f" leave-out {j}: {borda([v for v in rows if v['judge']!=j])}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
run()
|
||||
125
eval/exp14_regate.py
Normal file
125
eval/exp14_regate.py
Normal file
|
|
@ -0,0 +1,125 @@
|
|||
#!/usr/bin/env python3
|
||||
"""exp14 §3 — Fidelity re-gate пере-прогона (D34.3, ОБЯЗАТЕЛЕН; независим от §1-§2).
|
||||
|
||||
Span-цитирующий, РАЗДЕЛЬНО стиль/верность, охота на КЛАСС ИНВЕРСИЙ РЕДАКТУРЫ (final vs draft
|
||||
vs src: билингв-редактор мог купить гладкость ценой смысла), катастроф-скрин терминов.
|
||||
author≠reviewer: судьи НЕ glm-5 (редактор) и НЕ deepseek (черновик) — кросс-семейно
|
||||
gpt-5-mini + kimi (+ gemini точечно на флагах). leave-one-out. $ персист.
|
||||
|
||||
Вход: rerun/records.json (source/draft/final). ch5.0/ch20.0 — final пуст (экспорт-баг D35.4a),
|
||||
скорятся отдельно (не в среднем). Выход: exp14/regate_verdicts.jsonl + свод.
|
||||
|
||||
Использование: exp14_regate.py [--dry] [--limit N]
|
||||
"""
|
||||
from __future__ import annotations
|
||||
import argparse, json, re, sys
|
||||
from pathlib import Path
|
||||
sys.path.insert(0, str(Path(__file__).resolve().parent))
|
||||
import exp14_common as C
|
||||
|
||||
RERUN = Path("/home/ubuntu/books/gu-zhenren/rerun")
|
||||
RECS = json.load(open(RERUN / "records.json"))
|
||||
JUDGES = ["gpt-5-mini", "kimi-k2.6"] # кросс-семейно к glm-5/deepseek; gemini — точечно вне этого прохода
|
||||
CAPS = {"gpt-5-mini": 0.06, "kimi-k2.6": 0.10, "gemini-3.1-pro-preview": 0.30}
|
||||
|
||||
SYS = ("Ты — билингвальный редактор-эксперт zh→ru, СУДЬЯ ВЕРНОСТИ. Тебе даны: ИСХОДНИК (zh), "
|
||||
"ЧЕРНОВИК (ru) и ФИНАЛ после редактуры (ru). Оцени ВЕРНОСТЬ ФИНАЛА исходнику РАЗДЕЛЬНО от стиля. "
|
||||
"Особо ищи КЛАСС ИНВЕРСИЙ РЕДАКТУРЫ: правки, где ФИНАЛ отошёл от смысла исходника ДАЛЬШЕ черновика "
|
||||
"(редактор внёс смысл, которого в исходнике нет; перевернул полярность/участника/число; выбросил атом). "
|
||||
"Ответь СТРОГО JSON без markdown: "
|
||||
'{"fidelity":0-100,"style":0-100,"n_mistranslation":int,"n_omission":int,'
|
||||
'"n_edit_inversion":int,"catastrophe":true|false,'
|
||||
'"flags":[{"type":"inversion|omission|term|addition","span_src":"≤12 слов zh",'
|
||||
'"quote_final":"≤15 слов ru","why":"кратко"}]}. '
|
||||
"catastrophe=true при инверсии полярности/участника/действия ИЛИ потере ключевого атома (заголовок/событие/термин).")
|
||||
|
||||
|
||||
def run():
|
||||
ap = argparse.ArgumentParser(); ap.add_argument("--dry", action="store_true")
|
||||
ap.add_argument("--limit", type=int, default=0); a = ap.parse_args()
|
||||
sp = C.Spender(C.DIAG / "regate_costs.jsonl", CAPS)
|
||||
vpath = C.DIAG / "regate_verdicts.jsonl"
|
||||
done = set(); out = []
|
||||
if vpath.exists():
|
||||
for l in vpath.read_text(encoding="utf-8").splitlines():
|
||||
if l.strip():
|
||||
v = json.loads(l); out.append(v); done.add((v["chunk"], v["judge"]))
|
||||
recs = [r for r in RECS if (r["chapter"], r["chunk_idx"]) not in {(5, 0), (20, 0)}
|
||||
and (r.get("final") or "").strip()]
|
||||
empties = [(r["chapter"], r["chunk_idx"]) for r in RECS if not (r.get("final") or "").strip()]
|
||||
if a.limit:
|
||||
recs = recs[:a.limit]
|
||||
tasks = [(r, jm) for r in recs for jm in JUDGES
|
||||
if (f"{r['chapter']}.{r['chunk_idx']}", jm) not in done]
|
||||
print(f"re-gate: {len(recs)} chunks × {len(JUDGES)} judges = {len(tasks)} new tasks "
|
||||
f"({len(done)} done); empty-final (экспорт-баг, вне скора): {empties}")
|
||||
if a.dry:
|
||||
est = sum(C.predict_cost(jm, 4000, 4000) for _, jm in tasks)
|
||||
print(f"predicted ≤ ${est:.2f}"); return
|
||||
for r, jm in tasks:
|
||||
cid = f"{r['chapter']}.{r['chunk_idx']}"
|
||||
user = (f"ИСХОДНИК (zh):\n{r['source']}\n\nЧЕРНОВИК (ru):\n{r['draft']}\n\n"
|
||||
f"ФИНАЛ после редактуры (ru):\n{r['final']}\n\nОцени верность ФИНАЛА. Ответь JSON.")
|
||||
# gpt-5-mini: reasoning ⊆ completion → нужен запас (medium@4000 съедал бюджет → empty); low@10000
|
||||
s = C.spec(jm, max_tokens=(16000 if jm == "kimi-k2.6" else 10000),
|
||||
reasoning_effort=("low" if jm == "gpt-5-mini" else None))
|
||||
in_est = C.count_tokens(SYS + user, "deepseek")
|
||||
ok, pred = sp.guard(jm, in_est, s["max_tokens"])
|
||||
if not ok:
|
||||
print(f" OVER-CAP {cid} {jm} pred=${pred:.3f}"); continue
|
||||
text, err, usage = C.call(s, [{"role": "system", "content": SYS},
|
||||
{"role": "user", "content": user}], timeout=300)
|
||||
cst = sp.record({"model": jm, "judge": jm, "keyenv": s["keyenv"], "chunk": cid,
|
||||
"err": err, "usage": usage})
|
||||
v = parse(text) if not err else {"fidelity": None, "err": err[:60]}
|
||||
v.update(chunk=cid, judge=jm)
|
||||
out.append(v)
|
||||
with open(vpath, "a", encoding="utf-8") as f:
|
||||
f.write(json.dumps(v, ensure_ascii=False) + "\n")
|
||||
fl = len(v.get("flags", []) or [])
|
||||
print(f" {cid:<7} {jm:<12} fid={v.get('fidelity')} style={v.get('style')} "
|
||||
f"cat={v.get('catastrophe')} flags={fl} ${cst:.4f}")
|
||||
summarize(out, empties)
|
||||
|
||||
|
||||
def parse(text):
|
||||
m = re.search(r"\{.*\}", text, re.S)
|
||||
if not m:
|
||||
return {"fidelity": None, "parse": text[:80]}
|
||||
try:
|
||||
d = json.loads(m.group(0))
|
||||
return {k: d.get(k) for k in ("fidelity", "style", "n_mistranslation", "n_omission",
|
||||
"n_edit_inversion", "catastrophe", "flags")}
|
||||
except Exception:
|
||||
f = re.search(r'"fidelity"\s*:\s*(\d+)', m.group(0))
|
||||
cat = '"catastrophe": true' in m.group(0).lower() or '"catastrophe":true' in m.group(0).lower()
|
||||
return {"fidelity": int(f.group(1)) if f else None, "catastrophe": cat, "parse_fallback": True}
|
||||
|
||||
|
||||
def summarize(out, empties):
|
||||
from collections import defaultdict
|
||||
import statistics
|
||||
byj = defaultdict(list); cats = []; invs = []
|
||||
for v in out:
|
||||
if v.get("fidelity") is not None:
|
||||
byj[v["judge"]].append(v["fidelity"])
|
||||
if v.get("catastrophe"):
|
||||
cats.append((v["chunk"], v["judge"]))
|
||||
for fl in (v.get("flags") or []):
|
||||
if fl.get("type") == "inversion":
|
||||
invs.append((v["chunk"], v["judge"], fl.get("quote_final", "")[:60]))
|
||||
print("\n=== FIDELITY RE-GATE свод ===")
|
||||
for j, xs in byj.items():
|
||||
print(f" {j}: mean fidelity {statistics.mean(xs):.1f} (n={len(xs)}, min={min(xs)})")
|
||||
if len(byj) == 2: # leave-one-out
|
||||
js = list(byj)
|
||||
print(f" leave-one-out: без {js[0]} → {statistics.mean(byj[js[1]]):.1f}; без {js[1]} → {statistics.mean(byj[js[0]]):.1f}")
|
||||
print(f" КАТАСТРОФ-флаги: {len(cats)} → {cats[:20]}")
|
||||
print(f" ИНВЕРСИИ РЕДАКТУРЫ: {len(invs)}")
|
||||
for c in invs[:15]:
|
||||
print(f" {c[0]} [{c[1]}]: {c[2]}")
|
||||
print(f" экспорт-баг пустые финалы (вне скора): {empties}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
run()
|
||||
122
eval/exp14_sizecurve.py
Normal file
122
eval/exp14_sizecurve.py
Normal file
|
|
@ -0,0 +1,122 @@
|
|||
#!/usr/bin/env python3
|
||||
"""exp14 §кривая — размер-чанка ↔ качество ↔ биллинг ↔ ретраи.
|
||||
|
||||
Пере-нарезка главы при разных бюджетах (жадная упаковка целых абзацев-строк до char-порога)
|
||||
+ whole-chapter. Пайплайн draft(flash thinking-ON)→edit(glm-5 дискурс). Снимаем РЕАЛЬНЫЕ
|
||||
in/out/reasoning-токены, латентность, ошибки-по-причинам, output-token-cost, KPI-абзацы.
|
||||
Chapter-level инъекция едина для всех сегментов (изолируем РАЗМЕР, не глоссарий).
|
||||
|
||||
Бюджет чанка отчитывается в ВЫХОДНЫХ токенах (реальный токенайзер glm-4.6). Оптимизация —
|
||||
retry-adjusted output-cost. Оговорка: у этой вебновеллы плоская структура (1 предл./строка,
|
||||
мало сцен-маркеров) → политика границ «сцена vs жадная» слабо различима; меряем жадную + whole.
|
||||
|
||||
Использование: exp14_sizecurve.py [--chapters 17,13] [--dry]
|
||||
"""
|
||||
from __future__ import annotations
|
||||
import argparse, json, re, statistics, sys, time
|
||||
from pathlib import Path
|
||||
sys.path.insert(0, str(Path(__file__).resolve().parent))
|
||||
import exp14_common as C
|
||||
import exp14_prompts as P
|
||||
|
||||
RERUN = Path("/home/ubuntu/books/gu-zhenren/rerun")
|
||||
RECS = {(r["chapter"], r["chunk_idx"]): r for r in json.load(open(RERUN / "records.json"))}
|
||||
INJ = json.load(open(C.DIAG / "injection_blocks.json"))
|
||||
SIZES = [800, 1600, 3200, 99999] # zh-chars; 99999 = whole-chapter
|
||||
OUT = C.DIAG / "sizecurve"; OUT.mkdir(exist_ok=True)
|
||||
CAPS = {"deepseek-v4-flash": 0.03, "glm-5": 0.08}
|
||||
|
||||
|
||||
def chapter_source(ch):
|
||||
cks = sorted(c for (cc, c) in RECS if cc == ch)
|
||||
return "\n".join(RECS[(ch, c)]["source"] for c in cks)
|
||||
|
||||
|
||||
def rechunk(src, budget):
|
||||
"""Жадная упаковка целых строк (абзацев) до budget zh-символов; строку не рвём."""
|
||||
lines = [l for l in src.split("\n") if l.strip()]
|
||||
segs, cur, n = [], [], 0
|
||||
for l in lines:
|
||||
if cur and n + len(l) > budget:
|
||||
segs.append("\n".join(cur)); cur, n = [], 0
|
||||
cur.append(l); n += len(l)
|
||||
if cur:
|
||||
segs.append("\n".join(cur))
|
||||
return segs
|
||||
|
||||
|
||||
def paras(t): return [p for p in re.split(r"\n\s*\n", t.strip()) if p.strip()]
|
||||
def sents(p): return [s for s in re.split(r"(?<=[.!?…])\s+", p.strip()) if s.strip()]
|
||||
def kpi(t):
|
||||
narr = [p for p in paras(t) if not p.strip().startswith("—")]
|
||||
sp = [len(sents(p)) for p in narr] or [0]
|
||||
return round(statistics.mean(sp), 2), round(sum(1 for x in sp if x == 1)/len(sp), 3)
|
||||
|
||||
|
||||
def run():
|
||||
ap = argparse.ArgumentParser(); ap.add_argument("--chapters", default="17,13")
|
||||
ap.add_argument("--dry", action="store_true"); a = ap.parse_args()
|
||||
chapters = [int(x) for x in a.chapters.split(",")]
|
||||
sp = C.Spender(C.DIAG / "sizecurve_costs.jsonl", CAPS)
|
||||
tsys = P.translator_system(); esys = P.editor_system("discourse")
|
||||
rows = []
|
||||
for ch in chapters:
|
||||
src = chapter_source(ch)
|
||||
einj = INJ.get(f"{ch}/ALL", {}).get("editor_constraint", "")
|
||||
ginj = INJ.get(f"{ch}/ALL", {}).get("bilingual_glossary", "")
|
||||
for budget in SIZES:
|
||||
segs = rechunk(src, budget)
|
||||
label = "whole" if budget == 99999 else f"{budget}c"
|
||||
tag = f"{ch}-{label}"
|
||||
outp = OUT / f"{tag}.txt"
|
||||
if outp.exists():
|
||||
print(f" [skip] {tag}"); continue
|
||||
print(f"\n=== ch{ch} size={label}: {len(segs)} сегментов ===")
|
||||
if a.dry:
|
||||
for s in segs:
|
||||
print(f" seg {len(s)}zh-chars")
|
||||
continue
|
||||
final_parts, in_tot, out_tot, reason_tot, seg_costs, errs, lat = [], 0, 0, 0, 0.0, [], 0.0
|
||||
for i, seg in enumerate(segs):
|
||||
# DRAFT (flash, thinking ON)
|
||||
ds = C.spec("deepseek-v4-flash", temp=0.3, max_tokens=8000)
|
||||
dmsgs = C.messages_with_injection(tsys, ginj, P.translator_user(seg))
|
||||
t0 = time.time()
|
||||
draft, derr, dusage = C.call(ds, dmsgs, timeout=300)
|
||||
sp.record({"model": "deepseek-v4-flash", "keyenv": ds["keyenv"], "tag": tag,
|
||||
"seg": i, "stage": "draft", "err": derr, "usage": dusage})
|
||||
if derr:
|
||||
errs.append(f"draft:{derr[:30]}"); draft = seg # фолбэк — исходник (эхо-гард поймает)
|
||||
# EDIT (glm-5 discourse)
|
||||
es = C.spec("glm-5", temp=0.4, max_tokens=8000)
|
||||
emsgs = C.messages_with_injection(esys, einj, P.editor_user(seg, draft))
|
||||
final, eerr, eusage = C.call(es, emsgs, timeout=360)
|
||||
c = sp.record({"model": "glm-5", "keyenv": es["keyenv"], "tag": tag, "seg": i,
|
||||
"stage": "edit", "err": eerr, "usage": eusage})
|
||||
lat += round(time.time() - t0, 1)
|
||||
if eerr:
|
||||
errs.append(f"edit:{eerr[:30]}"); final = draft
|
||||
final_parts.append(final)
|
||||
in_tot += (dusage.get("prompt_tokens", 0) or 0) + (eusage.get("prompt_tokens", 0) or 0)
|
||||
out_tot += (dusage.get("completion_tokens", 0) or 0) + (eusage.get("completion_tokens", 0) or 0)
|
||||
full = "\n\n".join(final_parts)
|
||||
outp.write_text(full, encoding="utf-8")
|
||||
ru_out_tok = C.count_tokens(full, "glm")
|
||||
mean_spp, share1 = kpi(full)
|
||||
han = sum(1 for ch_ in full if "一" <= ch_ <= "鿿")
|
||||
row = dict(chapter=ch, size=label, n_seg=len(segs), in_tok=in_tot, out_tok=out_tot,
|
||||
ru_out_tok=ru_out_tok, cost=round(sp.by_key.get("ZAI_API_KEY", 0)+sp.by_key.get("DEEPSEEK_API_KEY", 0), 4),
|
||||
mean_spp=mean_spp, share_1sent=share1, han=han, errs=errs, latency_s=round(lat, 1))
|
||||
rows.append(row)
|
||||
print(f" segs={len(segs)} in={in_tot} out={out_tot} ru_out_tok={ru_out_tok} "
|
||||
f"mean_spp={mean_spp} share1={share1} CJK={han} errs={len(errs)} lat={lat:.0f}s")
|
||||
(OUT / "sizecurve.json").write_text(json.dumps(rows, ensure_ascii=False, indent=1), encoding="utf-8")
|
||||
if rows:
|
||||
print("\n=== КРИВАЯ (per chapter×size) ===")
|
||||
print(f"{'ch-size':<12}{'segs':>5}{'out_tok':>9}{'mean_spp':>9}{'share1':>8}{'CJK':>5}{'errs':>5}")
|
||||
for r in rows:
|
||||
print(f"{r['chapter']}-{r['size']:<8}{r['n_seg']:>5}{r['out_tok']:>9}{r['mean_spp']:>9}{r['share_1sent']:>8}{r['han']:>5}{len(r['errs']):>5}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
run()
|
||||
Loading…
Add table
Reference in a new issue