From f4e7124c4bf8aaa584cafdfd27ae7d5e280165d0 Mon Sep 17 00:00:00 2001 From: "Claude (backend session)" Date: Sun, 5 Jul 2026 20:58:09 +0300 Subject: [PATCH] Consolidate config: wire grok-4.3 as the editor with an explicit reasoning_effort:none off-switch (control:effort), keeping DeepSeek's echo-mine no-op, and mark escalation stubs interim --- backend/configs/models.yaml | 12 ++++--- backend/configs/pipeline-c1.yaml | 15 +++++--- backend/configs/pipeline-c2.yaml | 10 ++++-- backend/internal/config/echo_mine_test.go | 43 +++++++++++++++++++++++ backend/internal/llm/capability.go | 12 +++++-- backend/internal/llm/capability_test.go | 14 +++++++- 6 files changed, 90 insertions(+), 16 deletions(-) diff --git a/backend/configs/models.yaml b/backend/configs/models.yaml index 796eecf..049a6d0 100644 --- a/backend/configs/models.yaml +++ b/backend/configs/models.yaml @@ -3,7 +3,6 @@ # # Источники проверки 2026-07-04 (воркфлоу-фактчек, официальные страницы): # DeepSeek https://api-docs.deepseek.com/quick_start/pricing -# Anthropic https://platform.claude.com/docs/en/about-claude/pricing # Z.AI/GLM https://docs.z.ai/guides/overview/pricing # Kimi https://platform.kimi.ai/docs/pricing/chat-k26 # xAI https://docs.x.ai/docs/models (Grok 4.1 Fast retired 15.05.2026!) @@ -107,14 +106,17 @@ models: # не верифицировали, а для редактора (черновик — свежий вход на чанк) cache-hit≈0, # поэтому консервативно не моделируем скидку — потолок не слепнет. Уточнит полигон. price: { input_per_m: 1.25, cached_per_m: 1.25, cache_write_per_m: 0, output_per_m: 2.50 } - # Дефолт-редактор (bake-off эксп-04): thinking OFF (0 reasoning-токенов, ~13с), temperature шлём. + # Дефолт-редактор (bake-off эксп-04): temperature шлём; reasoning OFF даётся ТОЛЬКО явным + # reasoning_effort:"none" (control:effort + off_effort:none). Дефолт grok = low → омиссия + # заставляет думать: проба eval 05.07 (Chat Completions) — omission→444 reasoning-ток., "none"→0. + # control:none (off-by-omission) молча оставил бы low; control:effort/off_effort шлёт явный none. capabilities: temperature: { mode: send } - reasoning: { control: none } + reasoning: { control: effort, off_effort: none } note: >- Дефолт-редактор SFW (D3, bake-off эксп-04) + главный пермиссив-тир канала B + судья 18+. - reasoning additive: think-ON только после резерва в EstimateUSD (D6.2/техдолг F3). Заведена, - в стадию не включена — переключение editor→grok в следующем конфиг-шаге (после подтверждения прод-ключа). + reasoning additive: think-ON только после резерва в EstimateUSD (D6.2/техдолг F3). Включена + в edit-стадию C1/C2 (D1/D3, консолидация 05.07); прод-прогон — только с ЧИСТЫМ xAI-ключом без data-sharing. # claude-sonnet-5 / claude-opus-4-8 удалены вместе с провайдером anthropic # (см. комментарий у providers выше). Судья Фазы 2 — Gemini (нативный адаптер). diff --git a/backend/configs/pipeline-c1.yaml b/backend/configs/pipeline-c1.yaml index 113d8c4..cf1622b 100644 --- a/backend/configs/pipeline-c1.yaml +++ b/backend/configs/pipeline-c1.yaml @@ -41,7 +41,10 @@ stages: reasoning: "off" - name: edit role: editor - model: glm-5 + # Дефолт-редактор — grok-4.3 (D1/D3, bake-off эксп-04; glm-5 был плейсхолдером). + # reasoning:"off" → grok шлёт ЯВНЫЙ reasoning_effort:"none" (control:effort/off_effort:none, + # models.yaml) — омиссия у grok = low. Прод: только чистый xAI-ключ без data-sharing. + model: grok-4.3 prompt: ../prompts/editor.md prompt_version: v0-draft temperature: 0.4 @@ -61,10 +64,12 @@ gates: escalation: chains: - # Anthropic убран из стека (владелец, 04.07). Провизорная не-Anthropic - # эскалация — Kimi K2.6; финальную модель эскалации (после отказа от - # Anthropic — кандидаты GLM-5.1/5.2, Gemini) выбирает оркестратор к Фазе 1. - # В Фазе 0 эскалация не исполняется (гейты off). + # ⚠️ ИНТЕРИМ-ЗАГЛУШКА, НЕ контракт. Ратифицированная D3-цепочка: + # канал A: deepseek-v4-pro → glm-5.1 → gemini-3.1-pro; канал B: grok-4.3. + # Эти модели и провайдеры gemini/openai ещё НЕ заведены в models.yaml (цены/ключи + + # пересчёт cost-model полигоном гейтят заводку) → пока провизорный kimi-k2.6. + # Заводка D3-цепочки — отдельный шаг. В Фазе 0 / при gates off эскалация не + # исполняется (CheckKeys не требует её ключей, пока budget_usd=0). default: [kimi-k2.6] adult: [kimi-k2.6] # Адресный премиум-бюджет книги, $. Формула «≤2×» из Р5 неоднозначна после diff --git a/backend/configs/pipeline-c2.yaml b/backend/configs/pipeline-c2.yaml index e79ebb0..3611684 100644 --- a/backend/configs/pipeline-c2.yaml +++ b/backend/configs/pipeline-c2.yaml @@ -40,7 +40,10 @@ stages: reasoning: "off" - name: edit role: editor - model: glm-5 + # Дефолт-редактор — grok-4.3 (D1/D3, bake-off эксп-04; glm-5 был плейсхолдером). + # reasoning:"off" → grok шлёт ЯВНЫЙ reasoning_effort:"none" (control:effort/off_effort:none). + # (Стадия select выше — judge/glm-5, Gemini-слот Фазы 2; её НЕ трогаем.) + model: grok-4.3 prompt: ../prompts/editor.md prompt_version: v0-draft temperature: 0.4 @@ -58,7 +61,10 @@ gates: escalation: chains: - default: [kimi-k2.6] # Anthropic убран; финальная модель эскалации — оркестратор, Фаза 1 + # ⚠️ ИНТЕРИМ-ЗАГЛУШКА, НЕ контракт. Ратифицированная D3: канал A deepseek-v4-pro → + # glm-5.1 → gemini-3.1-pro, канал B grok-4.3 (эти модели/провайдеры gemini/openai в + # models.yaml не заведены — цены/ключи; заводка — отдельный шаг). Провизорный kimi-k2.6. + default: [kimi-k2.6] adult: [kimi-k2.6] budget_usd: 0 diff --git a/backend/internal/config/echo_mine_test.go b/backend/internal/config/echo_mine_test.go index e87dad7..6c682e0 100644 --- a/backend/internal/config/echo_mine_test.go +++ b/backend/internal/config/echo_mine_test.go @@ -164,3 +164,46 @@ func TestBoevoyConfigDeepSeekThinkingStaysOn(t *testing.T) { t.Fatalf("%s re-arms the echo mine: %s", model, why) } } + +// TestBoevoyConfigGrokEditorExplicitNone pins the consolidation fix (tasks 1-3): grok-4.3 +// resolves to an EXPLICIT reasoning off-switch (ReasoningEffortField + OffEffort "none"), NOT +// off-by-omission — grok's OMITTED default is "low" (it thinks, billed additively per xAI, so +// control:none would silently cost ~444 reasoning tokens/edit). It must not trip the echo-mine +// guard (xAI is not echo-prone: the editor input is a Russian draft), and the edit stage of both +// boevoy pipelines must be grok-4.3, while c2's select (judge) stage stays the glm-5 placeholder. +func TestBoevoyConfigGrokEditorExplicitNone(t *testing.T) { + m, err := LoadModels(filepath.Join("..", "..", "configs", "models.yaml")) + if err != nil { + t.Fatalf("load boevoy models.yaml: %v", err) + } + const model = "grok-4.3" + cap := m.ResolveCapability(model) + if cap.Reasoning.Control != llm.ReasoningEffortField || cap.Reasoning.OffEffort != "none" { + t.Errorf("%s must resolve to explicit reasoning_effort:\"none\" (control=effort, off_effort=none), got control=%q off_effort=%q — control:none leaves grok's \"low\" default (thinks, additive billing)", + model, cap.Reasoning.Control, cap.Reasoning.OffEffort) + } + if why := m.echoMineViolation(model); why != "" { + t.Errorf("%s must not trip the echo-mine guard (xAI editor input is a Russian draft, not CJK): %s", model, why) + } + for _, pf := range []string{"pipeline-c1.yaml", "pipeline-c2.yaml"} { + p, err := LoadPipeline(filepath.Join("..", "..", "configs", pf), m) + if err != nil { + t.Fatalf("load %s: %v", pf, err) + } + var editModel, selectModel string + for _, st := range p.Stages { + switch st.Role { + case "editor": + editModel = st.Model + case "judge": + selectModel = st.Model + } + } + if editModel != "grok-4.3" { + t.Errorf("%s edit stage model = %q, want grok-4.3 (D1/D3 default; glm-5 was the placeholder)", pf, editModel) + } + if pf == "pipeline-c2.yaml" && selectModel != "glm-5" { + t.Errorf("%s select (judge) stage must stay glm-5 (Gemini Phase-2 slot, untouched by task 3), got %q", pf, selectModel) + } + } +} diff --git a/backend/internal/llm/capability.go b/backend/internal/llm/capability.go index 10c2097..56e5912 100644 --- a/backend/internal/llm/capability.go +++ b/backend/internal/llm/capability.go @@ -50,9 +50,15 @@ type ReasoningControl string const ( // ReasoningNone is the OpenAI-compat baseline: low|medium|high go out as - // reasoning_effort; off and "" emit nothing (the provider's own default). - // Fits models where thinking is off by omission (DeepSeek / xAI-Grok on the - // Phase-1 draft/edit path). + // reasoning_effort; off and "" emit nothing, leaving the provider's OWN default. + // Use it for a model whose thinking must NOT be pushed off that default from our + // side: DeepSeek-flash on the draft path defaults to thinking ON, and "off" here is + // a deliberate NO-OP (emitting nothing keeps it ON) — DISABLING DeepSeek thinking + // arms the echo mine (it returns the untranslated CJK source at HTTP 200; + // config.echoMineViolation fail-fasts on it). This is the WRONG control for a model + // whose thinking is ON BY OMISSION: xAI-Grok defaults reasoning_effort to "low" (it + // thinks), so a role that must NOT think (the editor) uses ReasoningEffortField with + // OffEffort "none" to send an EXPLICIT reasoning_effort:"none" instead. ReasoningNone ReasoningControl = "none" // ReasoningEffortField maps "off" to a fixed OffEffort value on the // reasoning_effort field (gpt-5: "minimal"; local ollama: "none"). diff --git a/backend/internal/llm/capability_test.go b/backend/internal/llm/capability_test.go index 865d853..12618d8 100644 --- a/backend/internal/llm/capability_test.go +++ b/backend/internal/llm/capability_test.go @@ -84,6 +84,18 @@ func TestCapabilityWireBody(t *testing.T) { mustStr(t, m, "reasoning_effort", "minimal") }, }, + { + // The grok-4.3 editor money-path (consolidation tasks 1-2): reasoning=off must send an + // EXPLICIT reasoning_effort:"none" — grok's OMITTED default is "low" (it thinks, billed + // additively). A test that only checked the param is ABSENT would pass on the buggy + // control:none wiring; this pins the param is PRESENT and equals "none" (eval's flagged hole). + name: "grok editor: control=effort off_effort=none sends explicit reasoning_effort:none", + cap: Capability{Reasoning: ReasoningCap{Control: ReasoningEffortField, OffEffort: "none"}}, + effort: "off", + check: func(t *testing.T, m map[string]any) { + mustStr(t, m, "reasoning_effort", "none") + }, + }, { name: "gemini mandatory: swallow off, never disable, temperature sent", cap: Capability{Reasoning: ReasoningCap{Control: ReasoningMandatory}}, @@ -162,7 +174,7 @@ func TestCapabilityWireBody(t *testing.T) { extra: map[string]any{"max_tokens": 999, "custom": "x"}, check: func(t *testing.T, m map[string]any) { mustNum(t, m, "max_tokens", 4096) // resolved wins - mustStr(t, m, "custom", "x") // gap filled + mustStr(t, m, "custom", "x") // gap filled }, }, }