Scope CheckKeys to reachable models so backend runs on two keys; escalation keys required only when gates enabled
This commit is contained in:
parent
214e005155
commit
56670e3d67
5 changed files with 96 additions and 19 deletions
|
|
@ -1,10 +1,25 @@
|
|||
# Скопируй в backend/.env (gitignored) и заполни. tmctl подхватывает .env из
|
||||
# каталога book.yaml и из CWD; уже установленные переменные окружения не
|
||||
# перетираются.
|
||||
DEEPSEEK_API_KEY=
|
||||
ZAI_API_KEY=
|
||||
KIMI_API_KEY=
|
||||
ANTHROPIC_API_KEY=
|
||||
#
|
||||
# Два набора моделей в проекте служат РАЗНЫМ задачам:
|
||||
# backend/.env — продакшн-стек Р4, ФАЗОВО (нужно только то, что реально
|
||||
# зовётся в текущей фазе);
|
||||
# eval/.env — бенчмарк-сеть сессии «Полигон» (широкий набор провайдеров
|
||||
# для замеров отказов/токенов), НЕ обязан совпадать с продакшн-стеком.
|
||||
|
||||
# --- НУЖНЫ СЕЙЧАС (Фаза 0: пайплайн C1 draft→edit) ---
|
||||
DEEPSEEK_API_KEY= # черновик — DeepSeek V4 Flash (лучший zh-токенизатор, дешевле всех)
|
||||
ZAI_API_KEY= # редактура — GLM-5 (ru-стиль), международный контур z.ai
|
||||
|
||||
# --- ОПЦИОНАЛЬНО, Фаза 1+ (эскалация; зовутся только при включённых гейтах) ---
|
||||
# KIMI_API_KEY= # альтернативный редактор / эскалация канала adult (Р4)
|
||||
# ANTHROPIC_API_KEY= # эскалация/судья SFW, ТОЛЬКО прямой контур; в RU-контуре
|
||||
# экономически недоступен (Р5). Не нужен для Фазы 0.
|
||||
# Держи пустым, пока eval не подтвердит SFW-поведение
|
||||
# Sonnet-5 (эскалация «стерильного» канала — открытый
|
||||
# вопрос к полигону, см. PROGRESS).
|
||||
|
||||
# LOG_LEVEL=debug
|
||||
# LOG_FORMAT=json
|
||||
# LOG_LLM_BODIES=1 # логировать сырые тела LLM-обменов (только с LOG_LEVEL=debug)
|
||||
|
|
|
|||
|
|
@ -59,13 +59,23 @@ func TestCheckKeysOnlyUsedNonLocalModels(t *testing.T) {
|
|||
if err := m.CheckKeys(cloudPipe); err != nil {
|
||||
t.Fatalf("present key must pass: %v", err)
|
||||
}
|
||||
// Escalation-цепочки тоже учитываются.
|
||||
escPipe := &Pipeline{
|
||||
// Эскалационные ключи НЕ требуются, пока гейты выключены (эскалация
|
||||
// недостижима — Фаза 0): local-стадия + облачная цепочка при gates off.
|
||||
t.Setenv("TEST_CLOUD_KEY", "")
|
||||
escOffPipe := &Pipeline{
|
||||
Stages: []Stage{{Model: "local-model"}},
|
||||
Escal: Escalation{Chains: map[string][]string{"default": {"cloud-model"}}},
|
||||
}
|
||||
t.Setenv("TEST_CLOUD_KEY", "")
|
||||
if err := m.CheckKeys(escPipe); err == nil {
|
||||
t.Fatal("escalation chain model without a key must fail-fast")
|
||||
if err := m.CheckKeys(escOffPipe); err != nil {
|
||||
t.Fatalf("escalation keys must not be required while gates are off: %v", err)
|
||||
}
|
||||
// С включённым гейтом эскалация достижима → ключ цепочки обязателен.
|
||||
escOnPipe := &Pipeline{
|
||||
Stages: []Stage{{Model: "local-model"}},
|
||||
Escal: Escalation{Chains: map[string][]string{"default": {"cloud-model"}}},
|
||||
Gates: Gates{Coverage: CoverageGate{Enabled: true}},
|
||||
}
|
||||
if err := m.CheckKeys(escOnPipe); err == nil {
|
||||
t.Fatal("escalation chain model without a key must fail-fast when gates are enabled")
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -185,20 +185,29 @@ func (p Provider) APIKey() string {
|
|||
return os.Getenv(p.APIKeyEnv)
|
||||
}
|
||||
|
||||
// CheckKeys verifies that every non-local model REFERENCED by the pipeline has
|
||||
// its provider API key resolvable in the environment — preflight fail-fast,
|
||||
// чтобы пустой ключ не всплывал 401-м уже ПОСЛЕ reserve/списания слота
|
||||
// (находка ревью). Проверяются только используемые модели: local-only прогон
|
||||
// не должен падать на незаполненных облачных ключах. Escalation-цепочки тоже
|
||||
// учитываются — их модели вызываются в Фазе 1.
|
||||
// CheckKeys verifies that every non-local model the pipeline can ACTUALLY call
|
||||
// has its provider API key resolvable in the environment — preflight fail-fast,
|
||||
// чтобы пустой ключ не всплывал 401-м уже ПОСЛЕ reserve/списания слота.
|
||||
//
|
||||
// Проверяются только достижимые модели, не весь Р4-стек:
|
||||
// - модели СТАДИЙ — всегда (зовутся на каждом чанке);
|
||||
// - модели ЭСКАЛАЦИОННЫХ цепочек — ТОЛЬКО если включён хоть один гейт:
|
||||
// эскалация запускается исключительно по провалу гейта, поэтому при
|
||||
// gates.*.enabled=false (Фаза 0) она недостижима и её ключи не нужны.
|
||||
// Без этого условия перевод одного чанка (DeepSeek+GLM) требовал бы ещё
|
||||
// ключи Anthropic/Kimi из дефолтных цепочек, которые в Фазе 0 не зовутся.
|
||||
//
|
||||
// local-only прогон облачных ключей не требует.
|
||||
func (m *Models) CheckKeys(pipe *Pipeline) error {
|
||||
needed := map[string]struct{}{}
|
||||
for _, st := range pipe.Stages {
|
||||
needed[st.Model] = struct{}{}
|
||||
}
|
||||
for _, chain := range pipe.Escal.Chains {
|
||||
for _, mdl := range chain {
|
||||
needed[mdl] = struct{}{}
|
||||
if pipe.gatesEnabled() {
|
||||
for _, chain := range pipe.Escal.Chains {
|
||||
for _, mdl := range chain {
|
||||
needed[mdl] = struct{}{}
|
||||
}
|
||||
}
|
||||
}
|
||||
var problems []string
|
||||
|
|
|
|||
|
|
@ -93,6 +93,12 @@ type Fanout struct {
|
|||
Candidates int `yaml:"candidates"`
|
||||
}
|
||||
|
||||
// gatesEnabled reports whether any QA-gate is on. Эскалация запускается только
|
||||
// по провалу гейта, поэтому это сигнал «эскалация вообще достижима» (Фаза 1+).
|
||||
func (p *Pipeline) gatesEnabled() bool {
|
||||
return p.Gates.Coverage.Enabled
|
||||
}
|
||||
|
||||
// CheckRunnable rejects a config whose mechanics the Phase-0 runner does NOT
|
||||
// implement — раздельно от schema-валидации (LoadPipeline), чтобы скелеты C2/C3
|
||||
// парсились и валидировались как схема, но НЕ исполнялись молча. Без этого
|
||||
|
|
|
|||
|
|
@ -107,6 +107,38 @@ brief_hash, chunker/estimator version); перечанкование инвал
|
|||
timeout-путь «оплачено-но-потеряно» (≤1 вызов, укладывается в Р6); cache_ttl↔cache_write под один TTL (5m);
|
||||
гонка `Runner.clients` (безопасна при последовательном проходе, мьютекс — к fan-out Фазы 1–2).
|
||||
|
||||
**Ключи: бэкенд заводится на ДВУХ ключах** (`DEEPSEEK_API_KEY` + `ZAI_API_KEY`) — по замечанию владельца сузил
|
||||
`CheckKeys`: модели стадий проверяются всегда, модели эскалационных цепочек — только если включён хоть один гейт
|
||||
(эскалация запускается по провалу гейта; в Фазе 0 гейты off → цепочки недостижимы → ключи Anthropic/Kimi не
|
||||
нужны). Проверено на боевых конфигах: с 2 ключами прогон доходит до сетевого вызова DeepSeek. `.env.example`
|
||||
размечен «нужны сейчас / Фаза 1+ опционально». Anthropic/Kimi для Фазы 0 не требуются.
|
||||
|
||||
### Вопросы от бэкенда — сессии «Полигон» и оркестратору (04.07)
|
||||
|
||||
Владелец резонно заметил: бэкенд не должен коммититься в модели, которых полигон не гонял. Прежде чем в Фазе 1
|
||||
включать эскалацию и фиксировать редактора-дефолт, нужны замеры:
|
||||
|
||||
1. **Полигону — Anthropic на «стерильном» канале (SFW).** Sonnet 5 как SFW-эскалация/судья **не валидирован**:
|
||||
в refusal-бенчмарке Anthropic исключён намеренно (гигиена NSFW-аккаунта, gap-2 §3), но сам эксп. 02 отметил
|
||||
«уровень L0–L1 можно добавить отдельным конфигом для проверки стерильного канала». Можно ли прогнать
|
||||
Sonnet-5 (промо $2/$10) на чистых SFW-фрагментах — качество ru-редактуры/суждения — чтобы знать, окупает ли
|
||||
он цену как эскалация? Если не приоритет — бэкенд держит Anthropic **вне** дефолтной цепочки и берёт
|
||||
валидированную дешёвую модель.
|
||||
2. **Полигону — качество редактора ru-стиля.** Эксп. 02 мерил роль ЧЕРНОВИКА (отказы/вырезания), но не роль
|
||||
РЕДАКТОРА. Дефолт C1 — GLM-5 на редактуре — стоит на утверждении Р4, без замера. Планируется ли маленькое
|
||||
сравнение GLM-5 vs Kimi-K2.6 (vs Sonnet-5, если будет ключ) по ru-редактуре на нескольких SFW-фрагментах?
|
||||
От этого зависит дефолт редактора Фазы 1.
|
||||
3. **Оркестратору — эскалационный дефолт в RU-контуре.** Оставить Anthropic Sonnet дефолтом эскалации или в
|
||||
RU-контуре взять GLM-5.1/Gemini (Sonnet — только прямой контур, где per-book-премиум оправдан)? Это правка
|
||||
Р4/экономики; бэкенд — только исполнитель конфига.
|
||||
4. **Оркестратору — per-role provider-fallback?** Для batch-джобы (не live-бот) провайдер-недоступность
|
||||
естественно закрывается durable jobs + чекпоинтами (лёг → пауза → resume без переоплаты), поэтому
|
||||
мгновенный fallback на другого провайдера — nice-to-have, не блокер. Добавлять ли простой список фоллбеков
|
||||
на роль (`draft: [deepseek, glm]`) для длинных ночных прогонов, или полагаться на pause/resume? Решение — за
|
||||
оркестратором (владеет Р4/экономикой).
|
||||
|
||||
Не блокирует Фазу 0 (принята на DeepSeek+Z.ai); ответы нужны к старту Фазы 1 (эскалация, дефолты ролей).
|
||||
|
||||
Следующее: Фаза 1 (перевод книги целиком) — чанкер, банк памяти v1, гейты (пороги полигона готовы), режим онгоинга.
|
||||
|
||||
## Полигон
|
||||
|
|
@ -128,10 +160,15 @@ timeout-путь «оплачено-но-потеряно» (≤1 вызов, у
|
|||
- **Эксперимент 03 (задача 3) — первый замер + эталон** → [experiments/03-local-stand.md](experiments/03-local-stand.md). По п.3 вводных: (а) 30b-a3b в WSL **влезает** (mmap, без OOM), но на ollama даёт лишь **10 tok/s** — «Расхождение» с research/06 (25–30 обещаны через llama.cpp `--n-cpu-moe`; ставится в фоне — перемерить; Р4 менять пока не надо: рекомендация там и так llama-server); частично виноват и лимит WSL RAM 20GB — поднят до 26GB (`.wslconfig`, вступит после рестарта WSL); (б) 8b @ 27 tok/s (6.6GB VRAM) — на роли скрининга/экстракции годен. **Эталон DeepSeek снят: разрыв качественный** — API даёт корректные реалии («ворота Расёмон на улице Судзаку», «Новогоднее жертвоприношение») за ~$0.0005/фрагмент там, где обе локалки галлюцинируют («Рождественская дверь», «петухи»); NSFW-фрагменты ещё не гонялись. Для бэкенда: лег-таймаут vojo 45 с несовместим с чанками (63–278 с) — нужен свой профиль + стриминг. План расширения пула: ваниль qwen3:8b (цена абляции) → Qwen3.5-9B → RuadaptQwen3 (докачиваются в фоне).
|
||||
- Бэклог принят: проверка проброса cache-hit агрегаторами (п.4, нужны ключи) — в шагах ниже. Задачи 4–5 не начаты (bge-m3 уже на стенде).
|
||||
|
||||
### 2026-07-04 — Сессия 2 полигона (после рестарта WSL с RAM 26GB)
|
||||
|
||||
- **llama.cpp `--n-cpu-moe` для 30b-a3b — перемерено, «Расхождение» с research/06 подтверждено**: потолок генерации на GTX 1070 **~13.5 tok/s** (не 25–30). Таблица конфигов в [03](experiments/03-local-stand.md): ollama 10 → llama.cpp `--cpu-moe` 11.2 → `--n-cpu-moe 33` 13.5 (VRAM 2.9→7.8GB, prefill 105→208). Узкое место — пропускная способность CPU-RAM (DDR4/Pascal), не рантайм; llama.cpp честно лучше ollama (+35% ген., ×2–3 prefill), но 30b-a3b на этом железе — только фоновые роли (~2.5 мин/главу). Скрипт: `eval/llama_moe_bench.sh`.
|
||||
- **Замер 4 новых dense-моделей** (ваниль qwen3:8b / qwen3.5:9b / +abliterated / ruadapt) — готово, таблица в [03](experiments/03-local-stand.md). Три вывода: (1) **абляция бесплатна** — qwen3.5 vanilla vs abliterated идентичны по скорости и SFW-качеству → 18+-роль можно на abliterated без штрафа; (2) поколение 3.5>3 умеренно (лучше понимание, −25% токенов, −10% скорости); (3) **RuAdapt: токен-выигрыш подтверждён (−40% ru-токенов), но перевод развалился** (羅生門→«дерево с колокольчиками») → редактор ru-стиля, не переводчик (как и предупреждал research/06). Разрыв с API держится у всех. **Побочная находка для бэкенда:** thinking у Qwen3.5+/GLM/Gemini для роли переводчика обязателен к отключению (иначе пустой/обрезанный вывод — ловилось дважды: эксп. 02 и здесь).
|
||||
|
||||
### Следующие шаги полигона
|
||||
- [x] Прогон refusal-бенчмарка (SFW/L1/L2-violence часть) — 66/66 ok, пороги в 02; explicit-часть ждёт фрагментов владельца.
|
||||
- [x] Эталон DeepSeek для сравнения черновиков — в 03.
|
||||
- [ ] llama.cpp `--n-cpu-moe` для 30b-a3b — **сборка готова** (CUDA 12.6 user-space, пути/флаги в 03), замер после рестарта WSL с memory=26GB. Пул моделей для сравнения поколений/абляции/ru-адаптации докачан: qwen3:8b, qwen3.5:9b (+abliterated), ruadapt-qwen3:8b.
|
||||
- [x] llama.cpp `--n-cpu-moe` для 30b-a3b — перемерено (потолок 13.5 tok/s, см. выше).
|
||||
- [ ] Прогон Qwen Model Studio (`data_inspection_failed`) и OpenRouter — при появлении ключей.
|
||||
- [ ] Проверка ru-агрегаторов: проброс cache-hit DeepSeek (два одинаковых запроса, сравнить usage) — разблокирует экономику ru-контура.
|
||||
- [ ] Довалидация калибровки на 3–5 главах реальных вебновелл (файлы владельца → `eval/data/samples/` + manifest).
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue