# 00. Провайдерские грабли — справочник для адаптеров

Назначение: единый список практических особенностей LLM-провайдеров, найденных полигоном при живых вызовах. **Бэкенд-сессия читает это напрямую** при написании/правке адаптеров `internal/llm`, чтобы не переоткрывать в Go то, что уже поймано в Python. Дата: 2026-07-04, проверять при смене версий моделей.

Разделение ролей: полигон (`eval/`, Python) — тупой замерочный зонд, характеризует **сырое поведение** провайдера (отказ/качество/латентность); бэкенд (`backend/internal/llm`, Go) — продакшн-адаптеры (retry/failover/ledger). Код не общий (разные языки) и не должен быть — но знание о граблях общее и живёт здесь.

**⚠ ПРАВИЛО ДВУХ НАПРАВЛЕНИЙ (решение владельца 10.07, после Gemini-кейса):** (1) клейм о поведении модели → проверяй живой пробой (уже было правилом); (2) **аномалия на проводе (интермиттентные 4xx/5xx, «model no longer available», новый/неожиданный `finish_reason`, молча игнорируемые параметры) → СНАЧАЛА официальная дока вендора (deprecations / changelog / status page) + свежий /models-листинг, ПОТОМ диагноз.** Не гадать и не абсорбировать интерпретацию аномалии в доки/промты/адаптеры без вендор-сверки с датой и URL. Прецедент: 404-флейки gemini-2.5-flash интерпретировались вслепую, а офиц. дока сразу давала ответ — вся 2.5-серия EOL 16.10.2026 (см. календарь ниже).

## Эндпоинты и модели (OpenAI-совместимые, если не указано иное)

| Провайдер | base_url | Модель (на 2026-07-04) | env-ключ |
|---|---|---|---|
| DeepSeek | `https://api.deepseek.com/v1` | **`deepseek-v4-flash`** (`deepseek-chat` не в /models, но работает как алиас до депрекации 24.07.2026; эхает zh — см. ниже, брать v4-flash с thinking) | `DEEPSEEK_API_KEY` |
| xAI Grok | `https://api.x.ai/v1` | **`grok-4.3`** *(→ D30.1: из роли РЕДАКТОРА reasoning-off СНЯТ — no-op, exp12; grok — эскалация канала B reasoning-ON и судья 18+, не дефолт-редактор)* (для thinking-OFF слать явный `reasoning_effort:"none"` — см. thinking-таблицу ниже; слаг НЕ меняем на non-reasoning вариант — он = grok-4.3+`none` под капотом, ретайр 15.05.2026). `grok-4-fast` не в /v1/models, но работает как алиас (HTTP 200). | `XAI_API_KEY` |
| GLM (Z.ai) | `https://api.z.ai/api/paas/v4` | `glm-4.5-air` (дёшево) / `glm-4.6`; в /models: glm-4.7, glm-5, glm-5.1, glm-5.2 | `ZAI_API_KEY` |
| Gemini | `https://generativelanguage.googleapis.com/v1beta/openai` | `gemini-2.5-flash`, `gemini-2.5-pro`, `gemini-3.1-pro-preview` | `GEMINI_API_KEY` |
| Kimi (Moonshot) | `https://api.moonshot.ai/v1` (intl, **не** `.cn`) | `kimi-k2.6` (доступны k2.5, k2.7-code) | `KIMI_API_KEY` |
| OpenAI | `https://api.openai.com/v1` | `gpt-5-mini` | `OPENAI_API_KEY` |
| Qwen (DashScope intl) | `https://dashscope-intl.aliyuncs.com/compatible-mode/v1` | `qwen-flash` | `DASHSCOPE_API_KEY` |
| OpenRouter | `https://openrouter.ai/api/v1` | зависит от хостера | `OPENROUTER_API_KEY` |
| Локаль (ollama) | `http://localhost:11434/v1` (chat) или `/api/generate` | тег модели | не нужен |

## Thinking / reasoning — главный источник граблей

У всех современных моделей режим «размышления» включён по умолчанию и **ломает роль переводчика**: рассуждения съедают бюджет вывода → перевод обрезается или пустой, латентность ×3–5. **Для роли переводчика/редактора reasoning обязателен к управлению.** Как — зависит от провайдера:

| Провайдер | Поведение по умолчанию | Как управлять (для перевода) |
|---|---|---|
| **DeepSeek v4-flash** | гибридная reasoning-модель, thinking ON по умолчанию | **Управление (официальные доки, api-docs.deepseek.com/guides/thinking_mode, проверено 04.07): в body `extra_body={"thinking":{"type":"enabled"\|"disabled"}}`** (дефолт enabled). Т.е. выключить МОЖНО, параметр есть. НО для роли ПЕРЕВОДЧИКА не выключать: `disabled`→non-thinking режим, который на плотном CJK **ЭХАЕТ исходник**. Оставлять thinking ВКЛ; reasoning→`reasoning_content`, `content` чистый; `max_tokens` ≥8000 (иначе reasoning съест бюджет → `content` пуст, finish=length — это НЕ отказ). ⚠ **thinking-режим НЕ поддерживает `temperature`/`top_p`/penalties** (шлём — молча игнорируются). `reasoning_effort`: только `high`/`max` (low/medium→high, xhigh→max); `none` НЕ существует → 400. Для дешёвой роли ЭКСТРАКЦИИ (не перевод) thinking можно выключить — echo бьёт по переводу, не по JSON-выводу (перепроверить). |
| GLM-4.5-air / 4.6 | thinking ON, не влезал в 180с | отключить: `extra_body: {"thinking": {"type": "disabled"}}` |
| Gemini 2.5 Flash | thinking ON, съедал `max_tokens`, **молча обрезал перевод** | отключить: `extra_body.google.thinking_config.thinking_budget: 0` |
| **Gemini 3.1 Pro** | thinking **обязателен** | `thinking_budget:0` → HTTP 400 «only works in thinking mode». НЕ отключать; дать большой `max_tokens` (≥8000) |
| gpt-5-mini (reasoning) | reasoning ON, выжигал бюджет → пустой ответ | `reasoning_effort: "minimal"`; **без `temperature`**; `max_completion_tokens` вместо `max_tokens` |
| **gpt-5.4 (reasoning)** | ⚠ **`reasoning_effort:"medium"` РЕПРОДУЦИРУЕМО ОБРЕЗАЕТ ВЫВОД на длинной редактуре** (exp14b: reasoning съел бюджет → `content` 223 out-ток ≈ 602 симв из ~5000, `finish=stop` — выглядит как валидный короткий ответ, НЕ length-обрыв → тихо портит результат) | В роли РЕДАКТОРА/переводчика (длинный выход) слать **`reasoning_effort:"low"`** (exp14b: тот же чанк → 5137 симв, полный); `max_completion_tokens` (не `max_tokens`), без `temperature`. reasoning⊆completion (subset-биллинг). Слаг live-фактчек 2026-07-11: `gpt-5.4-2026-03-05`. *(Верифицировано span-читкой D38; self-review полигона поймал живьём.)* |
| **Kimi K2.6** | думающая, **очень многословная**: ~11k reasoning-токенов на абзац; reasoning в `reasoning_content`, ответ в `content`; при малом бюджете reasoning съедает лимит → `content` **пуст** (finish=length, не ошибка!) | дать `max_tokens` ≥16000; ответ из `content`. **Дорогой в роли редактора** — reasoning биллится как выход |
| **grok-4.3 (xAI)** | reasoning **ON по дефолту = `low`** (must-be-explicit: не задал → думает) | **Off-switch (Chat Completions) — ПЛОСКИЙ `reasoning_effort:"none"` в теле** (`reasoning_effort` ∈ {none,low,medium,high}). ⚠ **Вложенная форма `extra_body.reasoning.effort:"none"` — это Responses API; Chat Completions её МОЛЧА ГЛОТАЕТ** (остаётся дефолт `low`) — из-за этого exp08-проба ложно решила «не отключается». Проверено 05.07: `reasoning_effort:"none"`→`usage.reasoning_tokens=0`, дефолт→444, `low`→384 (на правке ~2000 ток.). reasoning у xAI **аддитивен к completion** (billed = completion+reasoning). `grok-4.20-0309-non-reasoning` = grok-4.3 + `effort:none` под капотом (ретайр 15.05.2026) → слаг не нужен, слать `none` явно. ⚠ **Для роли РЕДАКТОРА reasoning-off СНЯТ (D30.1): grok reasoning-off — no-op-редактор** (exp12 §2.2: активность 0.001, 3/6 чанков байт-идентичны черновику; exp04-ранг был на дефолт-reasoning + БИЛИНГВ). Если grok когда-либо вернётся в редакторы — только reasoning-ON (D6.2-буфер). Для роли ПЕРЕВОДЧИКА на плотном CJK reasoning-off НЕПРИГОДЕН (D19.1: эхо 4/10 + дегенеративный луп; ⚠ языковой констрейнт в system при `none` ИНВЕРТИРУЕТ эхо 3/10→9/10 — research/15 P4) — эскалация канала B идёт reasoning-ON. *(Сужено оркестратором по D19.1/D21; было «editor/translator».)* Источник: docs.x.ai/developers/model-capabilities/text/reasoning. |
| Qwen3.5 / локальные | thinking ON | ollama: `think: false` + `num_predict` cap. Иначе рассуждения (в поле `thinking`, не `response`) забивают контекст |

**Находка (эксп. 03):** reasoning реально улучшает реалии/имена в переводе (羅生門: «Радзёмон»→«Рашо-мон»), но локально нежизнеспособен (8.6 мин/фрагмент). **Открытый вопрос для бэкенда: проверить think-ON на быстром облачном черновике/редакторе как рычаг качества.**

### Эхо как свойство КЛАССА, не квирк вендора (обобщение D19.2 / D21.9)

**«reasoning-off + плотный CJK-вход = зона эха».** Это свойство **класса** reasoning-моделей, а НЕ квирк отдельного вендора: воспроизведено на deepseek (non-thinking режим, `00`-эхо-бюллетень выше) и на **обоих** слагах grok при `reasoning_effort:"none"` (exp10/D19.1: 4/10 verbatim-эхо + 1 дегенеративный луп на zh-фрагментах насилия, `reasoning_tokens=0`; контроль тех же фрагментов при reasoning-ON — 0/10 эха, `reasoning_tokens>0`). Механика одна: без цепочки рассуждений модель на плотной CJK-прозе возвращает исходник вместо перевода — **валидный HTTP 200 с неправильным содержимым** (не ошибка API, не `content_filter`; ретраи не спасают, эхо стохастично per-fragment). Следствия:
- **Для роли ПЕРЕВОДЧИКА на плотном CJK reasoning-off непригоден в принципе** (не только у одного вендора) — канал B переводит reasoning-ON (grok) либо не-reasoning-моделью без эхо-мины (Mistral: проба zh→ru чисто, `cjk_share=0`). ⚠ **Register-оговорка (exp11/D22, дописано оркестратором):** reasoning-ON снимает эхо на СОВРЕМЕННОЙ прозе (совр. zh-вебновелла 0/10, совр. ja 1/6, en 0/6), но НЕ на архаичной плотно-ханьской (金瓶梅, минский байхуа: grok-ON эхо 4/6 при `reasoning_tokens` 349–847; ON vs OFF там неразличимы) — эскалацию на grok-ON не считать лекарством от эха на архаичных zh-текстах; Mistral на той же архаике чист 6/6.
- **Downstream echo-гейт (`untranslated_echo`, `cjk_share>0.15`) обязателен НАВСЕГДА** и промпт-митигациями НЕ заменяется — это последняя линия против тихого провала.
- ⚠ **Инверсия языкового констрейнта (research/15 P4):** языковая строка в `system` у reasoning-off модели может **УСИЛИВАТЬ** эхо, а не гасить его — на grok `reasoning_effort:none` одна формулировка констрейнта подняла эхо 3/10→9/10. Проверена **одна** формулировка на одном слаге; чувствительность к формулировке/модели неизвестна. → **Запрет (D21.6): не вносить языковые констрейнты в промпты reasoning-off путей без per-model замера.** (Латентное: `translator.md:10` уже несёт языковую строку, но едет только на thinking-ON DeepSeek — вне зоны инверсии; при заводке Mistral-канала B прогнать P4-реплику на боевом промпте.)

## Параметры сэмплинга

| Провайдер | Грабля |
|---|---|
| **Kimi K2.6** | принимает **только `temperature: 1`**; иное → HTTP 400 «only 1 is allowed for this model» |
| gpt-5-mini | `temperature` не принимается вовсе (см. выше) |
| Локаль (ollama `/v1`) | `top_k`/`min_p`/`repeat_penalty` **не пробрасываются** (ollama issue #11325) → запекать в Modelfile (паттерн `qwen3-vojo`). Для llama-server — расширить запрос этими полями |
| Локаль (ollama) | `max_tokens`/`num_predict` покрывает **thinking + ответ вместе** (в отличие от xAI, где thinking сверх лимита) |

## Контент-фильтры / safety

- **Gemini**: `safety_settings` через OpenAI-совместимый слой **не передаются** (HTTP 400 «Unknown name safety_settings»). Дефолт фильтров у 2.5/3 — OFF (gap-2). Для явного контроля фильтров (роль судьи 18+) нужен **нативный** Gemini API, не OpenAI-слой. ⚠ **D22.6: на ГРАФИЧНОЙ эротике Gemini 3.x fail-closed `PROHIBITED_CONTENT` (8× wire-verified, exp11) — фильтр НЕконфигурируем, нативный API не спасает; судья эротики — только Grok. На violence/SFW Gemini-судья жив.** `finish_reason` приходит СОСТАВНОЙ строкой `'content_filter: PROHIBITED_CONTENT'` (точные матчеры мертвы — D22.8а). Биллинг Gemini: thinking ТОЛЬКО в `total_tokens` → `reasoning: additive_total`, иначе недоучёт 146×/вызов (D22.3). ⚠ **В роли редактора Gemini течёт сервис-преамбулой в выход («Вот отредактированный…», 6/6 чанков — exp12) → за output-санитайзером D30.3.**
- **Qwen Model Studio**: внешний фильтр `data_inspection_failed` на вход/выход, неотключаем (риск молчаливых вырезаний) — не тестировано (нет ключа).
- Детект отказов/вырезаний — см. `eval/refusal_bench.py` (паттерны soft-отказа + coverage-гейт по len_ratio/sent_cov, пороги в эксп. 02).

## Транспорт / инфраструктура

- **Прокси на localhost**: в env стенда webshare-прокси, `NO_PROXY=<local>` — WinINET-нотация, которую curl/urllib/Go **не понимают** → запрос к 127.0.0.1 уходит на прокси и получает **403**. Лечение: явный `NO_PROXY="localhost,127.0.0.1,0.0.0.0,::1"` + в коде обходить прокси для loopback/172.x (бэкенд: local-адаптер с no-proxy транспортом — уже сделано).
- **DeepSeek cache-поля**: в `usage` два варианта именования (бэкенд обрабатывает оба).
- **«Эхо» черновика (тихий отказ)**: deepseek-chat на плотной CJK-прозе **воспроизводимо** (3 из 3 ретраев на zh-фрагменте Лу Синя «祝福») возвращает **оригинал вместо перевода** (82% CJK в «переводе»). Не ошибка API — валидный ответ с неправильным содержимым; ретраи не помогают (детерминировано для фрагмента). Митигация в `eval/editor_bench.py`: детектор доли CJK (порог 15%) → фолбэк на другого провайдера (GLM перевёл тот же текст чисто). **Бэкенд/гейт: детектор CJK-артефактов в русском выходе обязателен** (смыкается с anti-omission coverage-гейтом Р7 и уже запланированным «детектором CJK-артефактов» — 7 из 18 моделей грешат); эскалация на другого провайдера, а не ретрай. NB: проверено на deepseek-chat; актуальную роль-модель `deepseek-v4-flash` перепроверить.
- **Таймауты**: книжные чанки на локали занимают **63–278 с** (не 45 с как в чат-vojo). Нужен per-роль лег-таймаут ~300–600 с + стриминг как keep-alive. Донорский транспорт vojo имел скрытый per-attempt потолок 60 с.

## Календарь деприкаций / цен (мониторить ежеквартально)

- `deepseek-chat` → `deepseek-v4-flash` к **24.07.2026**.
- **Gemini 2.5-серия (pro/flash/flash-lite) — shutdown 16.10.2026** («earliest possible date», офиц. deprecations-страница; live-фактчек оркестратора 09.07). Замены по вендору: 3.1-pro-preview / 3.5-flash / 3.1-flash-lite. ⚠ До даты на 2.5-flash уже наблюдён **интермиттентный 404 «model no longer available» при наличии слага в /models** (research/15, сырьё) — вендором не документирован; судья fidelity `memory_eval` сидит на 2.5-flash → мигрировать (D21.9). Отдельно: `finish_reason=PROHIBITED_CONTENT` на Gemini 3.x — **НЕконфигурируемый фильтр-класс** (safety_settings не влияют, в отличие от `SAFETY`); наблюдён на violence-сцене → вывод exp07 «content_filter мёртв» на Gemini 3.x не переносится. *(Добавлено оркестратором по research/15 + фактчеку.)*
- **Grok 4.1 Fast retired 15.05.2026** → слаги молча редиректят на `grok-4.3` (цена ×9). Ретайрнулся дешёвый тир, НЕ сам xAI: grok-4.3 остаётся основным пермиссивным тиром канала B.
- **Аудит /models 04.07.2026 (воркфлоу) — с поправкой на перепроверку:** и `deepseek-chat`, и `grok-4-fast` НЕ в списках /models (там v4-flash/v4-pro у DeepSeek; версионные слаги у xAI), НО **оба работают как алиасы** (перепроверено вживую: HTTP 200, переводят). Агент-аудитор написал «сняты» — это была неточность: «нет в /models» ≠ «не работает». deepseek-chat депрекируется 24.07.2026. У xAI есть мигрированные слаги-обёртки (`grok-4.20-0309-non-reasoning` / `-reasoning` / `-multi-agent`, ретайр 15.05.2026), но **для перевода/редактуры берём `grok-4.3` + явный `reasoning_effort:"none"`** (слаг не меняем; non-reasoning вариант = grok-4.3+`none` под капотом — см. thinking-таблицу). xAI `usage` отдаёт нестандартные `cost_in_usd_ticks`/`num_sources_used` — игнорировать. **Урок: имя модели проверять не только `/models`, но и пробным вызовом — алиас может работать, даже если его нет в списке; и наоборот.** Реальная причина сменить deepseek на v4-flash — не «chat умер», а ЭХО на zh (лечится thinking-on), см. раздел thinking.
- Claude Sonnet 5 промо $2/$10 до **31.08.2026** (не закладывать в долгий прайс).
- **ПОПРАВКА (04.07, владелец): удалён только Anthropic (за дороговизну). OpenAI ОСТАЁТСЯ** (ключ есть — GPT-5 nano/mini). Живой набор ключей у бэкенда и полигона: DEEPSEEK, ZAI, KIMI, OPENAI, GEMINI, XAI, MISTRAL (добавлен 09.07, 6ab92b5). Источник истины по стеку — `architecture/05-decisions-log.md` (D3) и Р4. Ранее эта строка ошибочно исключала OpenAI — исправлено оркестратором.

## Провенанс

Кто что нашёл: эндпоинты/thinking/temp/safety — полигон (эксп. 02, 03, 04, живые вызовы); Grok-retired/cache-write Anthropic/per-attempt-60с — бэкенд (шаг 0 валидации); localhost-прокси — оба стенда независимо.
