textmachine/docs/experiments/00-provider-quirks.md

70 lines
14 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 00. Провайдерские грабли — справочник для адаптеров
Назначение: единый список практических особенностей LLM-провайдеров, найденных полигоном при живых вызовах. **Бэкенд-сессия читает это напрямую** при написании/правке адаптеров `internal/llm`, чтобы не переоткрывать в Go то, что уже поймано в Python. Дата: 2026-07-04, проверять при смене версий моделей.
Разделение ролей: полигон (`eval/`, Python) — тупой замерочный зонд, характеризует **сырое поведение** провайдера (отказ/качество/латентность); бэкенд (`backend/internal/llm`, Go) — продакшн-адаптеры (retry/failover/ledger). Код не общий (разные языки) и не должен быть — но знание о граблях общее и живёт здесь.
## Эндпоинты и модели (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`** (дефолт-редактор/переводчик; для 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 — главный источник граблей
У всех современных моделей режим «размышления» включён по умолчанию и **ломает роль переводчика**: рассуждения съедают бюджет вывода → перевод обрезается или пустой, латентность ×35. **Для роли переводчика/редактора 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` |
| **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` явно. Для роли editor/translator — ВСЕГДА явный `reasoning_effort:"none"`. Источник: 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 на быстром облачном черновике/редакторе как рычаг качества.**
## Параметры сэмплинга
| Провайдер | Грабля |
|---|---|
| **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-слой.
- **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` перепроверить.
- **Таймауты**: книжные чанки на локали занимают **63278 с** (не 45 с как в чат-vojo). Нужен per-роль лег-таймаут ~300600 с + стриминг как keep-alive. Донорский транспорт vojo имел скрытый per-attempt потолок 60 с.
## Календарь деприкаций / цен (мониторить ежеквартально)
- `deepseek-chat``deepseek-v4-flash` к **24.07.2026**.
- **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. Источник истины по стеку — `architecture/05-decisions-log.md` (D3) и Р4. Ранее эта строка ошибочно исключала OpenAI — исправлено оркестратором.
## Провенанс
Кто что нашёл: эндпоинты/thinking/temp/safety — полигон (эксп. 02, 03, 04, живые вызовы); Grok-retired/cache-write Anthropic/per-attempt-60с — бэкенд (шаг 0 валидации); localhost-прокси — оба стенда независимо.