textmachine/docs/research/21-llm-transport-survey.md

283 lines
75 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.

# research/21 — Обзор LLM-транспорта чужих харнессов
> **РЕВЬЮ-ШАПКА ОРКЕСТРАТОРА (лендинг 23.07.2026).** Спот-чек адверсариальный, author≠reviewer: 7/7 несущих цитат о НАШЕМ коде подтверждены по file:line (`httpllm.go` jitter/клиент/429/parseRetryAfter, `provider_openai.go` additive, `llm.go` vojo) + 5/5 выборочных цитат из клонов байт-в-байт (mistral-strip, opencode z.ai-overflow, GLM finish-словарь, zai cache_control, grok-build shared_http). **Эрратум:** §1.1/§2 пишут «Mistral → 400», улика (докстринг litellm) говорит **422** extra_forbidden — класс верен, код статуса в отчёте неточен. **Поправка применимости (проверено исполнением):** наш wire — строго single-shot `system+system+user` (`render.go:223-227`), assistant-сообщений/reasoning-полей не шлём → квирки «Mistral extra_forbidden» и «deepseek multi-turn 422» для ТЕКУЩЕГО пере-прогона ЛАТЕНТНЫ (станут несущими при continuation #17 / любом multi-turn); GLM-overflow — инпуты юнитов ≪ окна (гард = future-proofing, не сегодняшний риск). Идущий пере-прогон НЕ трогать. **Все рекомендации — DEFER до после пере-прогона; Q7 — кандидаты до вендор-сверки (правило 10.07), в `00-provider-quirks.md` НЕ абсорбированы.**
**Статус: ПРИНЯТ (залендён 23.07); черновик сдан ресёрч-сессией 23.07. §5-аддендум (арх-паттерны) дописан сессией и залендён вторым коммитом — спот-чек цитат обеих сторон (`capability.go:12-18` фолд-в-снапшот, `clients.go:15` BuildClient, `requestoption.go:80-85` замыкание) подтверждён; рефактор-вердикт «транспорт не трогаем» ратифицирован (D-лог, PROGRESS 23.07).** Дата: 2026-07-23. Роль — ресёрч (полигон-профиль). Периметр: `$0`-сессия чтения кода, ни одного вызова моделей, `backend/` read-only. Материал — 11 склонированных харнессов в `/home/ubuntu/projects/tmp/`. Рекомендации применяются ТОЛЬКО после пере-прогона, отдельным решением. Все Q7-кандидаты — именно **кандидаты**: абсорбирует оркестратор ПОСЛЕ вендор-сверки (правило двух направлений, 10.07).
> **→ ПОСТРОЕНО паком-12 «транспорт-гигиена» (24.07, `7fe0a5b`; отчёт `docs/archive/reports/TRANSPORT_PACK12_REPORT_2026-07-24.md`; отметка 25.07, оркестратор №7):** §1-пункты 16/8a/8b/9 реализованы (quota-429→terminal · дробный Retry-After · HTTP/2 keepalive · джиттер · xAI identity-гард · GLM finish-нормализация · пин retryable-мапы · кап billed-decode · models.yaml-валидация); пункты 7 (CostSource) и 10 (луп-гард) — паком-13 (`2f91b04`). DEFER снят; Q7-абсорбция в `00-provider-quirks.md` — по-прежнему только после вендор-сверки.
## Метод, лицензии, надёжность
- **Оркестрация.** 11 репо-агентов (survey Q1Q6) ∥ 6 провайдер-агентов (quirk-mining Q7-код) → 6 синтез-агентов (по вопросу) + Q7-дедуп → адверсариальная верификация (author≠reviewer): 57 агентов, 0 ошибок. Онлайн-жатва трекеров (Q7) — отдельно, из главной сессии (`gh` + WebFetch).
- **Самопроверка исполнением поймала баг (мандат 12.07 в действии).** Синтез-агенты Q1 (prompt-cache — вопрос №1) и Q3 (finish_reason) вернули **плейсхолдер-заглушки** («test»), схема их не отсеяла — ровно тот класс, о котором предупреждает мандат. Сырьё survey по Q1 (18 находок) и Q3 (17 находок) — целое; Q1/Q3-синтез пере-выведен вручную из survey-находок + **независимая адверсариальная верификация 8 несущих цитат свежими агентами** (все CONFIRMED, детали ниже). Два REFUTED в авто-верификации — это и есть те заглушки; в отчёт не вошли.
- **Дисциплина цитат.** Каждая находка о чужом коде — с `repo/file:line`; спорное верифицировано чтением второго места (call-site/тест). Пометки [CONFIRMED]/[PARTIAL] — вердикт адверсариального ре-чтения (PARTIAL = паттерн реален, но формулировка/оценка завышена — см. per-question). НИКАКОГО копирования чужого кода: только описание паттернов.
**Лицензии (per-repo, апстрим):**
| repo | апстрим | язык | лицензия | примечание |
|---|---|---|---|---|
| codex | openai/codex | Rust | Apache-2.0 | Responses API only |
| gemini-cli | google-gemini/gemini-cli | TS | Apache-2.0 | @google/genai |
| grok-build | xai-org/grok-build | Rust | Apache-2.0 (© SpaceXAI) | **официальный xAI**, open-sourced 15.07.2026 |
| goose | block/goose | Rust | Apache-2.0 | declarative-провайдеры |
| aider | Aider-AI/aider | Python | Apache-2.0 | обёртка над litellm |
| cline | cline/cline | TS | Apache-2.0 | @anthropic-ai/sdk + AI-SDK |
| opencode | sst/opencode | TS | MIT | Vercel AI SDK |
| litellm | BerriAI/litellm | Python | **MIT-ядро** (`enterprise/` — иная лиц., **НЕ читалась**) | самый богатый провайдер-слой |
| crush | charmbracelet/crush | Go | **FSL-1.1** (читать можно, **копировать нельзя**) | транспорт делегирован во внеш. `charm.land/fantasy` |
| openai-go | openai/openai-go | Go | Apache-2.0 | референс SDK |
| anthropic-sdk-go | anthropics/anthropic-sdk-go | Go | MIT | референс SDK |
**Итог одной строкой.** Наш транспорт в базовой форме **опережает или вровень** со всеми 11: единый `retryLoop`, honored-Retry-After с 5-мин капом, fail-loud terminal 4xx, billed-decode-типизация, per-adapter reasoning-семантика, served-model биллинг, double-count-гард, min_max_tokens-флор — то, что другие имеют частично или не имеют. Значимого рычага против Mistral-48% или тихого усечения ни один харнесс **не даёт**. Ценность обзора — в **точечных догоне** (fractional Retry-After, quota-429→terminal, HTTP/2 keepalive, GLM-cache_control, native-Gemini usage) и в **Q7-квирках, прямо задевающих пере-прогонные армы glm/mistral/deepseek-pro**.
---
## §1. Топ-рекомендации (ранжированы по ожидаемому импакту)
Формат: приоритет · [вердикт] · рекомендация · **какая метрика двинется и на сколько** · гейт. «Держим как есть» с внешним подтверждением — тоже вывод (мы там ведём).
### Надёжность / тихий провал (высший приоритет — задевает пере-прогонные армы)
1. **[VERIFIED, NEW] Mistral: strip output-only полей перед отправкой.** Mistral — строгая `extra_forbidden`-схема входа: реплей `reasoning_content`/`thinking_blocks` или сообщения с `null`-значениями → **HTTP 400**. Наш билингвальный редактор возит src→dst-блок; если сборщик когда-либо приложит reasoning-поле (эскалация/второй хоп), **mistral-арм редактора упадёт**. Метрика: **надёжность mistral-editor-арма — из «падает на первом же запросе с reasoning-полем» в 0**; ноль COGS. Гейт: проверить наш message-builder перед тем, как mistral-арм пойдёт прод-путём (он и так за rate-guard, D39.12). Ev: `litellm/litellm/llms/mistral/chat/transformation.py:392-405` (`_strip_output_only_fields`, assistant-only).
2. **[VERIFIED, NEW] GLM молча глотает context-overflow.** z.ai/GLM, по комментарию opencode, может **принять переполненный контекстом промпт без 4xx** — модель просто усекает вход. Для нашего несущего glm-5-редактора это значит: волатильный хвост (инъекция банка памяти) **тихо отваливается**, и чистый 2xx отгружает деградированную правку без единого сигнала ошибки — ровно «сломанный телефон», против которого арх-ресет D39. Метрика: **качество editor-арма** — конвертирует тихий обвал в громкий фейл; предотвращает молчаливое усечение банк-хвоста. Действие: pre-send гард «оценка токенов vs `context_window`» для GLM (Capability может нести `context_window`), + маппинг context-length 400/413 → отдельный terminal `ContextOverflow`. Гейт: одна-харнессовая примета (opencode-комментарий, не wire-трасса) → **вендор-сверка z.ai обязательна** перед абсорбцией; порог оценщика — консервативный. Ev: `opencode/packages/opencode/src/provider/error.ts:30-31`; goose `is_context_length_exceeded_message` `goose/crates/goose-providers/src/http_status.rs:181-249`.
3. **[VERIFIED, NEW] GLM finish-reasons: `sensitive`→content_filter, `network_error`→stop.** GLM отдаёт нестандартные finish-строки вне OpenAI-словаря. `sensitive` — это контент-фильтр GLM (наш редактор — GLM!), а `network_error`, отмаппленный в `stop`, — **риск тихого провала** (сетевую ошибку выдаёт за чистое завершение). Наш контракт `finish=stop-only` + echo/coverage-гейты это ловят downstream, но матчер finish должен знать оба значения. Метрика: **корректность finish/undercount** — GLM-фильтр перестаёт выглядеть как валидный stop. Ev: `litellm/litellm/litellm_core_utils/core_helpers.py:120-122`.
4. **[CONFIRMED] Quota/credits-429 → отдельный TERMINAL, не retryable.** Сейчас ВСЕ 429 retryable (`httpllm.go:341-342`): ключ с кончившимися кредитами в середине волны сжигает 3 попытки + до 3×5мин honored-Retry-After под удержанной USD-резервацией — и всё равно падает. opencode и goose классифицируют это как fatal (детерминизм: оператор должен пополнить, ретрай не поможет). Метрика: **надёжность/wall-clock — fail-loud за 1 попытку вместо 3+backoff; волна не виснет до ~15мин** на мёртвом ключе. Гейт: узкий body-marker (`insufficient_quota`/`quota_exceeded`, +402) + drift-guard-тест (#12). Ev: opencode `packages/llm/src/route/executor.ts:242-245` + `schema/errors.ts:87-96`; goose `http_status.rs:181-249` (402→`CreditsExhausted`).
5. **[CONFIRMED] HTTP/2 keepalive на общем `http.Client` (без стриминга!).** На длинной thinking-тяжёлой правке НИ ОДНОГО байта тела не течёт, пока весь JSON не готов → прокси/LB может idle-закрыть сокет посреди генерации. Наш общий клиент (`httpllm.go:161-174`) собран **без keepalive-тюнинга**. HTTP/2 PING держит транспорт живым независимо от DATA-фреймов. Метрика: **надёжность — убирает класс mid-generation RST на длинных editor-вызовах**; каждый несостоявшийся reset = несостоявшийся ре-билл. Гейт: проверить, что фронты платных провайдеров не реджектят HTTP/2 PING (иначе HTTP/1.1 для них). Ev: grok-build `shared_http.rs:86-96` (http2 keepalive 15s/5s); openai-go `default_http_client.go:10-14` (ResponseHeaderTimeout).
6. **[VERIFIED, NEW] Gemini `limit: 0` в 429/499/503 = ЖЁСТКИЙ terminal, не транзиент.** Судья Gemini: 429, чьё сообщение содержит `limit: 0`, — проект с нулевой квотой, ретрай **никогда** не пройдёт (жжёт время). gemini-cli матчит `/limit:\s*0/` и делает terminal независимо от статуса. Метрика: **надёжность judge — не крутим 3 бесполезные попытки на нулевой квоте.** Ev: `gemini-cli/packages/core/src/utils/googleQuotaErrors.ts:238-249`.
7. **[VERIFIED, NEW] xAI: `x-should-retry:false` в ответе форсит FATAL поверх статуса.** Официальный grok-харнесс чтит header `x-should-retry` как авторитет над кодом: провайдер может пометить content-caused 5xx неретраебельным (экономит до `MaxAttempts1` ПЛАТНЫХ попыток). Три реализации (openai-go, anthropic-sdk-go, grok-build) сходятся. Дёшево, безвредно при отсутствии header (наши OpenAI-compat ноги в основном его не шлют; xAI/OpenAI — шлют). Метрика: **$ / надёжность — до 2 платных ретраев сэкономлено на server-marked-fatal 5xx; no-op при отсутствии.** Ev: openai-go `internal/requestconfig/requestconfig.go:383-388`; anthropic-sdk-go `internal/requestconfig/requestconfig.go:262-267`; grok-build `retry.rs:188-197`.
8. **[VERIFIED, NEW] Локаль/ollama: HTTP 200 с ошибкой В ТЕЛЕ стрима.** ollama возвращает 200, даже когда стрим несёт `error`-поле — успех/провал судится по событию, не по коду. Наш local-адаптер/liveness (WSL2-стенд) должен парсить тело, не только статус. Метрика: **надёжность local-fallback — ложный «жив» при битой локали.** Ev: codex `codex-rs/ollama/src/client.rs:190-197,227-235`.
### Деньги / точность биллинга
9. **[PARTIAL→реально] xAI reasoning: identity-гард против латентного 3044% overcount.** Наш `additive`-путь (`provider_openai.go:101-102`) добавляет `reasoning_tokens` **безусловно**. litellm фолдит их только когда `total == prompt+completion+reasoning` (иначе reasoning уже внутри completion). Сегодня xAI Chat Completions — additive-режим (наш верен), но гард делает нас **само-корректирующимися вместо «по слагу»**. Метрика: **xAI output-COGS overcount — 0% сегодня, но кап латентного ~3044% двойного счёта** (та самая drift-величина, что vojo уже оплатил, наш `llm.go:42`), если xAI когда-нибудь свернёт reasoning в completion. Гейт: живая grok-usage-проба в `00-provider-quirks.md` (правило двух направлений). Ev: `litellm/litellm/llms/xai/chat/transformation.py:289-353`; grok-build `xai-chat-state/src/usage.rs:44-61` (читает reasoning как **subset** — но это ДРУГОЙ эндпоинт, xAI-native, не Chat Completions).
10. **[CONFIRMED] CostSource/Estimated-маркер на settle-estimate строках.** Честную половину (settl'им резерв-оценку, не $0) мы уже делаем (`stagerun.go:469-473`, `422-436`). Не хватает легенды телеметрии: goose несёт `CostSource::{ProviderReported,Estimated}`, crush ставит `EstimatedUsage=true`. Без флага estimated-строки неотличимы от реальных zero-token вызовов и косят token-based COGS-аналитику. Метрика: **целостность телеметрии — «доля estimated в спенде» становится queryable; никаких безмолвных null-token строк.** Дисциплина: estimated-токены **display-only**, НЕ кормят `CostUSD`. Ev: goose `conversation/token_usage.rs:17-22`; crush `internal/agent/agent.go:1896-1927`.
11. **[CONFIRMED] План: судья Gemini → нативный `generateContent`.** На OpenAI-compat-слое Gemini роняет thoughts-поле → мы деривим reasoning вычитанием (`totalpromptcompletion`). Нативный эндпоинт отдаёт `usageMetadata.thoughtsTokenCount` первым классом **и структурный `finishReason`** вместо композитной строки `content_filter: PROHIBITED_CONTENT` — один переезд чинит и usage, и finish (пересечение с Q3). Метрика: **робастность reasoning-учёта Gemini** (нет derive-by-subtraction, который тихо даст 0, если слой перенесёт thinking в completion) + **корректность finish.** Гейт: за Ф2-нативным-Gemini (реальная транспорт-работа: auth/retry/cache_control); до него — additive_total + промежуточный inclusive-vs-additive identity-чек. Ev: litellm `vertex_and_google_ai_studio_gemini.py:1729-1745,1908-1944`; opencode `protocols/gemini.ts:338-361`.
### $ / cache (вопрос №1 — но импакт скромнее, чем звучит)
12. **[CONFIRMED] Держим Р5-раскладку; ГАРАНТИРУЕМ байт-идентичность константного префикса; НОВОЕ — cache_control на GLM.** Обзор подтверждает нашу раскладку с трёх сторон: (а) opencode инжектит cache-маркеры **только** для `anthropic-messages`/`bedrock-converse`, OpenAI/Gemini-implicit-cache **осознанно пропускает** (`cache-policy.ts:39-42`) — ровно наш сплит; (б) goose **физически переносит** волатильный блок (turn-context) в ХВОСТ, «иначе он инвалидирует message-level cached prefix (Anthropic хеширует tools→system→messages)» (`anthropic.rs:323-357`, CONFIRMED) — это и есть наша боль «инъекция банка бьёт префикс»: банк/STM **обязаны** идти строго после константного префикса; (в) litellm **срезает** cache_control для OpenAI-compat (`gpt_transformation.py:380-402`, дефолт-strip, с carve-out для кастом-base-гейтвеев). **Несущее уточнение:** `prompt_cache_key` (стабильный routing-ключ) есть у OpenAI-family/xAI/**Mistral**, но **НЕ у deepseek-native и НЕ у GLM** (litellm deepseek-dir: ноль ссылок; opencode эмитит ключ лишь для openai/azure/xai/mistral/deepinfra/cerebras) — значит для нашего draft (deepseek-v4-flash) и editor (glm-5) единственный рычаг — **литеральный префикс-автокэш**, и всё, что мы можем, — держать `system+brief+style-sheet+summary` байт-в-байт стабильным. **НОВОЕ (VERIFIED):** ZAI/GLM, в отличие от дефолтного OpenAI-compat-strip, **СОХРАНЯЕТ cache_control** (`litellm/litellm/llms/zai/chat/transformation.py:23-34`, «GLM supports cache_control — don't strip it») → большой стабильный билингв-префикс редактора (system/style/глоссарий) можно кэш-маркировать на GLM. Метрика: **вход-COGS редактора — cache-hit GLM $0.2 vs miss $1.0 (÷5) на входной доле**; НО вход ≪ выход (редактор доминирует выходом, `08-cost-model` §), поэтому реальная экономия **~2% стандарта** (как и предсказывал cost-model: «deepseek auto-cache ~2%, не рычаг»). Честно: Q1 — вопрос №1 по названию, но **низкий по $-импакту**; ценность — «мы уже правы» + один новый GLM-рычаг + фикс-гарантия стабильного префикса. Гейт: cache-цены/поведение GLM — вендор-сверка; mistral prompt_cache_key — тоже (см. Q7).
### Мелкое / отложенное (полнота)
13. **[CONFIRMED] `parseRetryAfter` — принять `Retry-After-Ms` и дробные секунды.** Наш `strconv.Atoi` (`httpllm.go:426`) режет любой не-integer → `Retry-After: 1.5`/`Retry-After-Ms` дают 0 и слепой exp-backoff. opencode/openai-go/anthropic-sdk-go все парсят ms первым. Метрика: **надёжность/латентность — чтит sub-second хинты вместо BackoffBase(500ms)+jitter**; чистый апсайд под нашим 5-мин капом. Ev: opencode `route/executor.ts:93-95`; openai-go `requestconfig.go:396-449`; anthropic-sdk-go `requestconfig.go:275-328`.
14. **[PARTIAL] Проба (не билд!): что DeepSeek/GLM/Mistral реально шлют на 429.** `obs.LogLLMExchange` уже пишет headers+body — проверить в пере-прогоне, есть ли `x-ratelimit-{limit,remaining,reset}`/body-retry-поле. opencode парсит эти окна, но тратит их на observability — рычаг «пейсить ДО 429» никто не построил. **Не строить пейсер сейчас** — решать по данным (правило двух направлений). Ev: opencode `route/executor.ts:112-148`; litellm `router.py:2823-2847` (pre-call RPM-чек внутри семафора — форма «пейсить до 429», если есть budget-header).
15. **[CONFIRMED] Держим rateGuard как есть; Mistral-48% = ТЮНИНГ `max_concurrency`, не алгоритм.** Весь обзор сошёлся: единственный другой проактивный лимитер — семафор litellm (`router.py:2823`), и он работает лишь потому, что у litellm есть sibling-деплойменты для роутинга вокруг 429 — чего у нас нет. Все прочие 10 — реактивны и влетели бы в наши 48%. **Умный backoff не чинит token-bucket/concurrency-cap тир** — только кап исходящей конкуренции ниже потолка тира. Метрика: **надёжность — mistral N-∥ retry-fail ~48%→~05%** при `max_concurrency` под потолком тира; ноль кода, ноль COGS. Гейт: per-tier калибровка; пере-замер при mistral-editor-арме. Ev: litellm `router.py:2823-2847`; goose `openai.rs:337-348` (Mistral — только max_tokens-remap, не rate).
16. **[PARTIAL, GATED] HTTP/1.1-escape на последней попытке — диагностика тихого усечения.** grok-build построил `send_with_retry_escaping_pool` (`xai-grok-http/src/lib.rs:432-475`) ровно чтобы уйти с «отравленного» HTTP/2-пула, и их truncation-регрессия — в том же модуле → **правдоподобная причина** нашего тихого усечения на 28k. Но корень НЕ подтверждён (может быть finish=length, который мы уже лечим) → CLAUDE.md запрещает гадать: **гейтить как эксперимент с замером h2-vs-forced-h1**, не лендить дефолтом. Метрика: truncation-rate 28k (baseline неизвестен — потенциально крупнейшая тихая утечка качества). Ev: grok-build `retry.rs:231-246`.
17. **[PARTIAL, DEFER] assistant-prefill continuation на finish=length.** Единственный механизм ЧАСТИЧНОГО восстановления во всех 11 (aider `base_coder.py:1492-1521`): вместо ре-рана всего вызова — дописать усечённый dst-блок как assistant-prefix и до-вызвать со свежим max_tokens. Наш finish=length сейчас ре-ранит весь запрос (`escalation.go:17-35`), пере-биллит уже сгенерированный префикс. Это **правильный ответ на «нужен стриминг для truncation» — не нужен, нужен continuation** (стриминг его НЕ даёт). Метрика: **$ — экономит ре-билл уже-эмитнутого префикса (~4060% editor-генерации) на событие усечения.** Гейт: **HIGH-риск для детерминизма** (мульти-хоп, golden/snapshot; наш src→dst-блок наивный сплайс может побить; Gemini/GLM prefix-continuation-семантика различна) → per-model `supports_assistant_prefill` + адверсариальный golden-ревью; **скорее DEFER**.
18. **[PARTIAL] Прочее мелкое, чистый апсайд/тесты:** (а) proportional jitter вместо fixed `rand(250ms)` (`httpllm.go:144`) — де-синк N-∥ на mistral (gemini-cli ±30%, openai-go 25%); (б) drift-guard тест, пиннящий status→retryable-мапу и finish-парсер как **префикс/substring**-матч (чтобы композит `content_filter: PROHIBITED_CONTENT` классифицировался) — аналог aider `_load`, который на импорте валит, если litellm `*Error` не расклассифицирован (`aider/exceptions.py:74-76`); (в) кап BilledDecodeError-ре-биллов ниже MaxAttempts (grok-build держит Serialization non-retryable, с регрессией «laundering в retryable дал full-budget шторм»).
**Явные REJECT (что НЕ берём — с причиной):**
- **Не берём** авто-своп модели на 429 (gemini-cli `action:'silent'`) — противоположно нашему fail-loud + ломает детерминизм; gemini-cli #23889 показал баг «сообщение дублируется 3050×» на pro→flash-фолбэке.
- **Не берём** 1h/uncapped honored-Retry-After (goose 3600s clamp; openai-go/anthropic-sdk-go uncapped) — виснет платная волна под USD-резервацией; наш 5-мин кап верен.
- **Не берём** no-jitter multiplicative backoff (aider) — thundering-herd под N-∥ волнами.
- **Не берём** `retry_429:false` (codex) — каждый tier-blip станет hard-fail в середине книги.
- **Не берём** идемпотентность-ключ: обзор отвечает на вопрос «у кого кроме OpenAI» — **НИ У КОГО не шлётся** (даже codex не шлёт OpenAI-у). Держим не-шлём.
- **Не берём** streaming ради «снижения обрывов/ре-биллов»: **ни один харнесс не резюмит частичную генерацию** — все ре-ранят весь запрос (codex full-history, gemini-cli full-turn, goose full-turn, opencode non-retryable break, anthropic no-resume). Стриминг покупает **ДЕТЕКЦИЮ**, не resume.
- **Не берём** брутальный body-substring/regex-стиль классификации ошибок (aider ~30-regex батарея) — та самая «exact matchers dead» хрупкость, что мы уже флагаем.
---
## §2. Q7 — таблица квирк-кандидатов
Все**кандидаты**, абсорбция после вендор-сверки (правило 10.07). Статус: **NEW** / **sharpens** (уточняет реестр) / **tracked** (только корроборация). «Verified» = адверсариально ре-прочитан в коде. Жатва трекеров помечена `#issue`+дата.
### Прямо задевает пере-прогонные армы (glm / mistral / deepseek-pro)
| Провайдер | Симптом | Статус | Ev (code / issue+дата) |
|---|---|---|---|
| **mistral** | Строгая `extra_forbidden`: реплей output-only `reasoning_content`/`thinking_blocks` или `null`-value сообщений → **400**; стрипать assistant-поля. | NEW·verified | `litellm mistral/chat/transformation.py:392-405` |
| **glm-zai** | Молча **принимает context-overflow** (нет 4xx) → тихо усекает вход. | NEW | `opencode packages/opencode/src/provider/error.ts:30-31` |
| **glm-zai** | finish `sensitive`→content_filter, `network_error`→stop (вне OpenAI-словаря; `network_error`→stop = риск тихого провала). | NEW·verified | `litellm core_helpers.py:120-122` |
| **glm-zai** | **СОХРАНЯЕТ cache_control** (в отличие от дефолт-strip OpenAI-compat) — COGS-рычаг для билингв-редактора. | NEW·verified | `litellm zai/chat/transformation.py:23-34` |
| **glm-zai** | Disable-thinking **эндпоинт-зависим**: native z.ai `thinking:{type:disabled}` vs routed OpenAI-compat `reasoning:{enabled:false}`. | sharpens·verified | `cline .../routing/glm-thinking.ts:31-58` |
| **glm-zai** | Множественные base URL: OpenAI-compat `/api/paas/v4` И Anthropic-Messages `/api/anthropic` (goose гоняет thinking через второй). | NEW | `goose .../declarative/definitions/zai.json:3,13,20` |
| **deepseek** | Thinking-ON **multi-turn**: 422 «reasoning_content must be passed back» — эхать reasoning_content на каждом assistant-сообщении. | NEW·verified | `litellm deepseek/chat/transformation.py:62-100` (issue #28045); litellm#28461 (2026-05-21), #26395 (2026-04-24) |
| **deepseek** | v4-flash thinking-ON отдаёт `"reasoning": null` вместо `reasoning_content` — нестабильность имени поля. | NEW | litellm#28461 (2026-05-21) |
| **mistral** | Отвергает `max_completion_tokens`, требует legacy `max_tokens` (мы уже шлём max_tokens). | tracked·verified | `goose openai.rs:335-348` |
| **mistral** | `seed` не под тем именем — детерминизм через `extra_body.random_seed`. | NEW·verified | `litellm mistral/chat/transformation.py:179-180` |
| **mistral** | Поддерживает prompt-caching через `prompt_cache_key` (стабильный id). | NEW | `opencode patches/@ai-sdk%2Fmistral@3.0.51.patch:80-81` |
| **deepseek** | base по умолчанию расходится: litellm `/beta`, crush+наш реестр `/v1` (у /beta — устаревший комментарий рядом = ровно наша «code-language» ловушка). | NEW·verified | `litellm deepseek/chat/transformation.py:261,278` |
### xAI / Gemini / Kimi (судья, канал B, кандидаты-редакторы)
| Провайдер | Симптом | Статус | Ev |
|---|---|---|---|
| **xai-grok** | reasoning-учёт **эндпоинт-split**: Chat Completions = ADDITIVE (`total=prompt+completion+reasoning`), Responses/native = subset. Наш путь — Chat → additive верен. | sharpens·verified | `litellm xai/chat/transformation.py:289-353`; grok-build `xai-chat-state/usage.rs:44-61` (subset) |
| **xai-grok** | `x-should-retry:false` в ответе → FATAL поверх статуса (даже 429/5xx). | NEW·verified | `grok-build client.rs:207-227 + retry.rs:176-190` |
| **xai-grok** | HTTP/2-пул «отравляется» (тихие LB/Cloudflare/GOAWAY-дропы) → ретрай на том же коннекте фейлит одинаково; лечат HTTP/1.1-rebuild. | NEW | `grok-build retry.rs:231-246; xai-grok-http/lib.rs:432-475` |
| **xai-grok** | Slug-gated param-дропы: grok-3-mini/grok-4/grok-code-fast отвергают `stop`; grok-4/code-fast — `frequency_penalty`. | NEW·verified | `litellm xai/chat/transformation.py:150-169` |
| **xai-grok** | Пустая строка finish_reason на tool-call (вместо `tool_calls`); чинят при наличии tool_calls. | NEW·verified | `litellm xai/chat/transformation.py:223-232` |
| **xai-grok** | Давится message-level `name`-полем (стрипать, issue #9720). | NEW·verified | `litellm xai/chat/transformation.py:207-221` |
| **xai-grok** | `num_sources_used` (Live Search) = **$0.025/источник** ($25/1000); датированная семантика к реестровому «игнорировать нестандартные cost-поля». | sharpens | `litellm xai/cost_calculator.py:55-84` |
| **gemini** | `candidatesTokenCount` **включает thinking НЕ всегда** — варьирует per-response; litellm детектит динамически, иначе ±учёт. | sharpens·verified | `litellm vertex_and_google_ai_studio_gemini.py:1729-1745` (PR#10141) |
| **gemini** | `MALFORMED_FUNCTION_CALL`: litellm→`stop`, opencode→`error` (харнессы РАСХОДЯТСЯ). Маппинг unknown→`stop` = **риск тихого провала**. | NEW·verified | `litellm core_helpers.py:100-124` |
| **gemini** | 2.5 «minimal» reasoning не достигает нуля: per-model floor (pro=128, flash=1, flash-lite=512); 3.x — `thinkingLevel`-enum, полностью не отключить. | sharpens | `litellm .../gemini.py:823-838,867-913`; constants.py:144-151 |
| **gemini** | 3.x требует/предпочитает `temperature:1.0` (харнесс инжектит, если не задано). | NEW | `litellm .../gemini.py:1238-1241` |
| **gemini** | `limit: 0` в 429/499/503 = terminal, не транзиент. | NEW | `gemini-cli utils/googleQuotaErrors.ts:238-249` |
| **kimi** | Reasoning-kimi (k2.5/k2.6): корректно **ОМИТить** temperature (не форсить 1). Наш K2.6 — reasoning → кандидат сменить `force:1`→omit. | sharpens·verified | `litellm moonshot/chat/transformation.py:131-146`; cline#10544 (2026-05-04, `400 invalid temperature: only 1 is allowed`) |
| **kimi** | Multi-turn tool-call: reasoning_content обязателен на каждом assistant-tool-сообщении (litellm вставляет `' '`). | NEW | `litellm moonshot/chat/transformation.py:148-192` (issue #23765) |
| **kimi** | reasoning-OFF = `thinking:{type:disabled}` (как GLM); k2.6 — `thinking.type`. | NEW | `cline .../routing/provider-option-rules.ts:344-379` |
| **kimi** | Дефолтный host расходится: goose `api.moonshot.cn`, litellm/наш реестр `api.moonshot.ai` (intl). | tracked | `goose .../moonshot.json:13-14` |
### Инфра / прочее
| Провайдер | Симптом | Статус | Ev |
|---|---|---|---|
| **ollama (local)** | HTTP 200 с `error` в теле стрима — судить по событию, не коду (наш local-liveness). | NEW·verified | `codex codex-rs/ollama/src/client.rs:190-197,227-235` |
| **glm-zai** | crush health-probe special-case: для ZAI «connected» = любой статус кроме 401 (наш pre-run live-probe: для GLM 401/403=down, прочее=up). | NEW | `crush internal/config/config.go:937` |
| **deepseek** | content нельзя слать OpenAI list/blocks — только плоская строка (мы шлём строки — не баг сейчас). | NEW·verified | `litellm deepseek/chat/transformation.py:115-125` |
| **deepseek** | cache-поля `prompt_cache_hit_tokens`/`_miss_tokens` (hit+miss==prompt). | tracked·verified | `litellm types/utils.py:1648-1653` (реестр стр.66) |
**Дубликаты онлайн-жатвы (корроборация, не новьё):** Kimi temp=1 повторяется (K2.5 тоже) — cline#10544; deepseek multi-turn 400 — claude-code#68995 (2026-06-17); mandatory-thinking-эндпоинты реджектят disable — claude-code#65863 (2026-06-06), #69379 (2026-06-18); Gemini quota-429-фолбэк баг-склонен — gemini-cli#26002/#23889/#9248/#4646; xai reasoning_effort slug-специфичен — litellm#16204 (2025-11-03); Mistral empty-content → None — litellm#12197 (2025-07-01); Kimi K3 существует — litellm#33921 (2026-07-19, ре-чек слага к квартальному).
---
## §3. Per-question разборы
### Q1 — Prompt-cache дисциплина ($) · [Q1/Q3-синтез пере-выведен вручную + верифицирован]
**Наше сейчас.** Р5: стабильный префикс (system+brief+style-sheet+summary) несёт `CacheBoundary`; волатильный хвост (глоссарий+STM+чанк). Anthropic-адаптер маппит boundary→`cache_control{ephemeral,ttl}` (≤4), суммирует input+cache_read+cache_creation в PromptTokens; OpenAI-compat игнорят флаг, автокэш по префиксу; `cacheRead()` читает оба варианта DeepSeek-полей.
**Cross-repo (все high-confidence).** Три семейства: (1) **Anthropic-маркерное** (grok-build 1 ephemeral на last-system; aider — блок-порядок + маркеры на стабильных блоках + warming-ping каждые `5*605`с под 5-мин TTL, `base_coder.py:1348/1340-1373` CONFIRMED; cline — last-user-текст + фейковый `' '`-парт чтобы маркер не схлопнулся; opencode — 4-breakpoint budget в invalidation-порядке tools→system→messages `anthropic-messages.ts:511-538`; litellm — config-driven `cache_control_injection_points`; anthropic-sdk-go — per-block ephemeral 5m/1h). (2) **OpenAI-family — только ключ:** codex/opencode/openai-go `prompt_cache_key` (стабильный routing-ключ, заменяет `user`), стрип cache_control (litellm `gpt_transformation.py:380-402`, дефолт-strip с carve-out для кастом-base). (3) **Gemini — контекстный кэш вне промпта:** litellm требует **один непрерывный блок** (`vertex_ai/context_caching/transformation.py:21-48`, CONFIRMED); gemini-cli explicit-кэш вообще не создаёт.
**Ключевые уточнения (VERIFIED свежими агентами):** goose **физически переносит** волатильный turn-context в хвост, «иначе инвалидирует cached prefix» (`anthropic.rs:323-357`) — прямой ответ нашей боли; `prompt_cache_key` работает у OpenAI-family/xAI/Mistral, но **НЕ deepseek-native/GLM**; GLM **сохраняет** cache_control (`zai/chat/transformation.py:23-34`).
**Берём/не берём.** Берём: (1) держать константный префикс байт-идентичным, банк/STM строго в хвост — рек #12; (2) cache_control-маркировка на GLM — новый рычаг; (3) для Gemini-explicit-кэша (если Ф2) — cached-блок должен быть непрерывным. Не берём: `prompt_cache_key` для draft/editor (deepseek/glm его не чтут). **$-импакт скромен (~2% стандарта)** — вход ≪ выход редактора; Q1 «вопрос №1» по важности вопроса, не по деньгам.
### Q2 — Rate-limit адаптив
**Наше сейчас.** `retryLoop` (exp backoff cap 30s, honored-Retry-After cap 5min, +rand(250ms), MaxAttempts 3) + отдельная ось: per-model `rateGuard` (семафор + min_interval, wire-neutral, не snapshot-folded). `parseRetryAfter` — только integer-header. Не читаем `x-ratelimit-*`, нет AIMD/cooldown. Mistral ~48% N-∥.
**Cross-repo.** Единственный другой проактивный лимитер — litellm семафор с pre-call RPM (`router.py:2823-2847`), работает лишь из-за sibling-деплойментов. opencode парсит retry-after-ms + `x-ratelimit-*`/`anthropic-ratelimit-*`, но тратит на observability. goose предпочитает body `retry_after_seconds` над header. gemini-cli — server-delay как **floor** backoff. codex Retry-After-header **не парсит вовсе** (только regex по тексту ошибки), `retry_429:false`. grok кап Retry-After 120s + hard-cap 429-ретраев=2.
**Берём/не берём.** Берём: fractional/ms Retry-After (#13), 429-header-проба (#14), body/message Retry-After fallback за vendor-гейтом (#18), опц. model-cooldown (defer). Держим: rateGuard + Mistral=тюнинг-кап (#15). Не берём: silent model-swap, 1h Retry-After, no-jitter backoff, retry_429:false. **Вывод: против Mistral-48% ни один харнесс не даёт алгоритма — только concurrency-cap работает.**
### Q3 — finish_reason таксономия · [пере-выведен вручную + верифицирован]
**Наше сейчас.** Нейтральные stop/length/content_filter/refusal; контракт finish=stop-only; length→ретрай с бо́льшим max_tokens (+ min_max_tokens-флор); Gemini композит `content_filter: PROHIBITED_CONTENT` (exact-матчеры уже мертвы).
**Cross-repo (все high).** litellm — единый статический `_FINISH_REASON_MAP`, **unmapped → warn + `stop`** (CONFIRMED; `MALFORMED_FUNCTION_CALL`→stop) — **риск тихого провала** (усечённую/битую/дегенеративную генерацию выдаёт за чистый stop). opencode — closed 6-value enum + **структурная детекция усечения** (`route/client.ts:382-391`, CONFIRMED: стрим без терминального finish-события → hard error). codex — Responses-события (`response.completed`/`.incomplete`; ранний close = ошибка). grok-build — 4-value StopReason + **server-side doom-loop-детектор** (`x-grok-doom-loop-check`; `DoomLoopDetected`→retry ≤250ms; CONFIRMED, «confident» = thinking-канал tail_repetition≤8) — релевантно нашей эхо/дегенеративной петле (research/15). cline — **reasoningFloor = budget+1024-reserve** (`gateway.ts:120-181`, CONFIRMED) — кросс-валидирует наш min_max_tokens. goose — **ОМИТит max_tokens** при unset (противоположно нашему флору; `openai.rs:1459-1474`, CONFIRMED). anthropic-sdk-go — богаче: +`model_context_window_exceeded`, `pause_turn`, `refusal`.
**Берём/не берём.** Держим: finish=stop-only + min_max_tokens-флор (cline подтверждает reserve-логику; наш флор — верный выбор для thinking-моделей, в отличие от goose-омита). Берём: finish-матчер как префикс/substring (drift-guard #18); знать GLM `sensitive`/`network_error` (§2). **Не берём** litellm-стиль «unknown→stop» — это ровно то, против чего наш строгий контракт + echo/coverage-гейты. Кандидат-заметка: xAI doom-loop-header как ранний сигнал дегенеративной петли (за vendor-сверкой). Структурная детекция усечения (opencode/codex) достижима **только со стримингом** → см. Q6.
### Q4 — Таксономия ошибок retry/fatal
**Наше сейчас.** Единый `retryLoop`: 429&≥500→retryable; прочие non-2xx→terminal fail-loud; network/timeout→retryable (unless ctx done); 2xx-undecodable→BilledDecodeError (retryable unless >16MiB); 2xx-empty→billed success. Идемпотентность — не шлём. Anthropic 529 ловится тем же ≥500.
**Cross-repo.** opencode — tagged-union, QuotaExceeded(429+body)=fatal, context-overflow под-классификация. goose — централизованная таблица (402→CreditsExhausted, 400/413→ContextLengthExceeded). openai-go/anthropic-sdk-go — **x-should-retry** override + body-rewind; retry 408/409/429/≥500. gemini-cli — network-code allowlist (undici timeouts), maxAttempts 10. aider — declarative EXCEPTIONS-таблица по классу litellm-исключения + `_load` drift-guard. codex — two-tier (transport 429/5xx/transport + семантик is_retryable whitelist; ServerOverloaded=terminal). litellm — Retry-After чтит только 0<x60s. crush/cline делегируют SDK.
**Берём/не берём.** Берём: quota-429terminal (#4), context-overflow terminal + GLM-precheck (#2), x-should-retry (#7), drift-guard-тест (#12/#18), кап billed-decode-ре-биллов, proportional jitter. Держим: network/timeout retryable, MaxAttempts 3, honored-Retry-After 5-мин-кап (не 60s litellm, не uncapped SDK), не-шлём идемпотентность, 529-в-500. Не берём: opencode non-retryable network/timeout, gemini maxAttempts 10, aider retry ContentPolicyViolation (наш 18+: детерминированный отказ, ретрай жжёт деньги), brittle-regex-стиль, 408/409.
### Q5 — Usage-учёт
**Наше сейчас.** Neutral Usage: PromptTokens(total), CachedTokens (оба DeepSeek-варианта), CacheCreationTokens, CompletionTokens, ReasoningTokens (subset/additive/additive_total). Anthropic суммирует 3 части. Served-model биллинг (`PriceForResponse(requested,served)`). Double-count-гард (uncached=promptcachedwrite≥0). Settl'им резерв-оценку на paid-2xx-zero-usage (не $0).
**Cross-repo.** litellm per-response reasoning-reconciliation с identity-гардами. grok-build (офиц.) биллит по SERVER cost-полю (`cost_in_usd_ticks`), reasoning отдельно/informational, `cost_missing_calls`-счётчик. opencode dual (inclusive + non-overlapping breakdown, undefined-not-zero). goose/crush/aider/cline usage-estimator fallback + CostSource-флаги; crush читает OpenRouter `Usage.Cost` override. codex/gemini-cli served-model через header/modelVersion.
**Берём/не берём.** Берём: xAI identity-гард (#9), CostSource-маркер (#10), план native-Gemini thoughtsTokenCount (#11), опц. server-cost-поле xAI (проба). Держим (мы ведём): served-model биллинг, double-count-гард, cache-fold, честный settle-estimate (не фабрикуем токен-математику как goose-estimator). Не берём: codex single-vendor usage-shape (мис-букнет xAI/Gemini), nested-only DeepSeek-cache (потеряет hit-скидку), aider DeepSeek-fallback (вычитает ЦЕНУ из ТОКЕНОВ живой мис-бук), streaming-usage-плюмбинг (мы non-streaming). Defer: split cache-creation 5m/1h (только если Anthropic вернётся / mixed-TTL).
### Q6 — Streaming vs non-streaming надёжность
**Наше сейчас.** Non-streaming ONLY (`StreamingLLMClient` отложен). Один attempt под `context.WithTimeout(attempt_s, дефолт 300; 240 конфиг)` покрывает connect+headers+body одним бюджетом. finish=length→ре-ран с бо́льшим max_tokens. Нет keepalive/idle-timeout/first-byte-timeout.
**Cross-repo.** codex/opencode/cline/crush/grok stream-only (buffered-caller дренит SSE); goose/gemini-cli/aider/litellm оба. **Ключевые примитивы, которые даёт только стриминг:** per-event **idle-timeout, сбрасываемый на каждом событии** (codex `responses.rs:503-529`); composed header+chunk+total через AbortSignal.any (opencode); **content-progress timer** (`last_content_chunk_at`, grok ловит thinking-stall/эхо-мину рано); **stream-closed-before-finish = error** (opencode/codex). anthropic-sdk-go **HARD-отказывает** длинный non-streaming (`CalculateNonStreamingTimeout`: если `expectedTime=1h·maxTokens/128000 > 10min` «streaming required»). openai-go ResponseHeaderTimeout=10min (только time-to-headers). Единственное частичное восстановление во всех 11 **assistant-prefill continuation** (aider), и оно на NON-streaming пути.
**Критический нюанс (обосновывает вердикт).** idle/stall-timeout, который все ценят, существует **только со стримингом**: на non-streaming пути нет inter-chunk-разрывов, сервер держит headers до конца генерации любой «first-byte/idle» коллапсирует в total attempt_s. Стриминг покупает **ДЕТЕКЦИЮ** (ранний catch зависа + stream-closed-signal + keepalive), **никогда автоматический RESUME**. По собственной математике Anthropic 28k выход ждёт ~24 мин, а её жёсткий non-streaming кап (Opus 8192) всего нашего диапазона `attempt_s=240` внутри сертифицированного вендором безопасного non-streaming конверта.
**Берём/не берём.** Держим non-streaming дефолтом + `StreamingLLMClient`-отсрочку, но с **явным trip-wire**: стриминг ТОЛЬКО для длинного editor-арма, ТОЛЬКО если тихое усечение сохранится на верху 28k на вебновелл-срезе, и **buffered** (fold SSEполный LLMResponse, opencode-паттерн сигнатура `Complete()`, golden/детерминизм, все гейты байт-идентичны). Если trip-wire сработает idle/stall-timeout как ЕДИНСТВЕННЫЙ новый механизм. Берём СЕЙЧАС (без стриминга): HTTP/2 keepalive (#5), assistant-prefill continuation-оценка (#17, defer). Не берём: streaming-only архитектуру (жрёт usage-fidelity aider падает на локальные ESTIMATE), idle-timeout в текущий non-streaming (коллапсирует в total), reclass empty-2xx как retryable (double-bill), стриминг ради снижения обрывов (никто не резюмит).
---
## §4. Appendix — per-repo (что смотрели / что пропустили)
- **codex** (Rust, Apache-2.0). Смотрели: `core/src/{client,client_common,responses_retry,util}.rs`, `codex-api/src/{sse/responses.rs,api_bridge.rs}`, `codex-client/src/retry.rs`, `model-provider-info/src/lib.rs`, `ollama/src/client.rs`, `protocol/src/error.rs`. Пропустили: TUI/apply-patch/cloud-tasks (не транспорт). Особенность: Responses-API-only, стрим-only, Retry-After-header не парсит.
- **gemini-cli** (TS, Apache-2.0). Смотрели: `core/src/core/{geminiChat,contentGenerator,loggingContentGenerator}.ts`, `utils/{retry,googleQuotaErrors}.ts`, `agent/event-translator.ts`, `utils/historyHardening.ts`. Пропустили: CLI/devtools/MCP. Особенность: model-fallback на 429 (баг-склонен), `limit:0`=terminal.
- **grok-build** (Rust, Apache-2.0, **офиц. xAI**). Смотрели: `xai-grok-sampler/src/{client,retry,shared_http, actor/request_task, stream/chat_completions}.rs`, `xai-grok-http/src/lib.rs`, `xai-grok-sampling-types/src/{conversation,doom_loop}.rs`, `xai-chat-state/src/usage.rs`. Особенность: doom-loop-детектор, HTTP/2-pool-escape, reasoning subset НА ЭТОМ (native) эндпоинте. Наиболее авторитетно по xAI-квиркам.
- **goose** (Rust, Apache-2.0). Смотрели: `goose-providers/src/{openai,openai_compatible,google,http_status}.rs`, `goose-provider-types/src/{retry,formats/{openai,anthropic,google},conversation/token_usage}.rs`, `goose/src/providers/usage_estimator.rs`, `declarative/definitions/*.json`. **Пропустили как ложный след:** `goose/src/tracing/rate_limiter.rs` пейсит OTLP-телеметрию, НЕ LLM (проверено вручную). Особенность: turn-context-релокейшн, declarative-провайдеры.
- **aider** (Python, Apache-2.0). Смотрели: `aider/{llm,sendchat,models,exceptions}.py`, `coders/{base_coder,chat_chunks}.py`. Особенность: обёртка над litellm; assistant-prefill continuation; warming-pings; `_load` drift-guard.
- **cline** (TS, Apache-2.0). Смотрели: `sdk/packages/llms/src/providers/{ai-sdk,gateway, routing/*}.ts`, `sdk/packages/shared/src/agent.ts`, `.../vendors/types.ts`. (Провайдер-хендлеры живут в `sdk/packages/llms`, не в `apps/vscode/src/core/api` там только `index.ts`.) Особенность: reasoningFloor, per-TTL cache-split, GLM/kimi thinking-routing.
- **opencode** (TS, MIT). Смотрели: `packages/llm/src/{cache-policy,route/{client,executor},protocols/*,schema/*,provider-error}.ts`, `packages/opencode/src/provider/{transform,error,provider}.ts`, `packages/core/src/util/retry.ts`. Особенность: compile-time cache-policy, структурная truncation-детекция, tagged-union ошибки.
- **litellm** (Python, MIT-ядро; **`enterprise/` НЕ читалась**). Смотрели: `litellm/{main,router,utils,cost_calculator,exceptions}.py`, `litellm_core_utils/{core_helpers,completion_timeout}.py`, `types/utils.py`, `llms/{deepseek,mistral,xai,moonshot,zai,openai,vertex_ai}/**`. Самый богатый провайдер-квирк-слой (Q3/Q4/Q5/Q7).
- **crush** (Go, **FSL-1.1 — read-only, не копировали**). Смотрели: `internal/agent/{agent,usage_fallback,loop_detection,errors}.go`, `internal/config/{provider,config}.go`, `internal/app/provider.go`. **Пропустили (внешнее):** реальный wire-транспорт в `charm.land/fantasy`/`catwalk` (не в репо) + `openai/openai-go/v3`. Особенность: OpenRouter `Usage.Cost` override, EstimatedUsage-флаг, ZAI-probe-special-case.
- **openai-go** (Go, Apache-2.0, референс). Смотрели: `client.go`, `default_http_client.go`, `option/{requestoption,middleware}.go`, `internal/requestconfig/requestconfig.go`, `internal/apierror/apierror.go`, `chatcompletion.go`. Референс: Retry-After-Ms, x-should-retry, proportional jitter, idempotency-key, PromptCacheKey.
- **anthropic-sdk-go** (Go, MIT, референс). Смотрели: `client.go`, `default_http_client.go`, `option/{requestoption,middleware}.go`, `internal/requestconfig/requestconfig.go`, `message.go`, `shared/constant/constants.go`. Референс: 529, x-should-retry, CalculateNonStreamingTimeout, per-TTL cache-split, model_context_window_exceeded.
---
## §5. Архитектурные паттерны — брать / не брать
Срез **АРХИТЕКТУРА** (слои, абстракции, регистрация провайдеров, data-vs-код), а не механика транспорта §1–§4. Три независимых адверсариальных чтения (authorreviewer, дозаказаны 23.07 по вопросу владельца): два Go-SDK (openai-go, anthropic-sdk-go), crush (Go), declarative-слой goose (Rust но паттерн язык-агностичен). Наша опора для сравнения: транспорт-шов `internal/llm`, провайдер-фабрика `BuildClient` (switch-on-kind, `backend/internal/pipeline/clients.go:15-53`), `models.yaml``Capability`-резолвер, свёрнутый в job-снапшот (`backend/internal/llm/capability.go:12-18`, D5.2).
### §5.1 Что вообще на Go и где у них транспорт
- **crush** (FSL-1.1) интерактивный кодинг-агент, ~50 internal-пакетов. **Свой wire-транспорт НЕ пишет**: делегирует внешним `charm.land/fantasy` (транспорт + generic agent-loop) + `charm.land/catwalk` (каталог провайдеров как данные, с 3-ярусной свежестью livediskembedded-at-release) + `openai/openai-go/v3`. Своё = policy/orchestration/state; квирки впрыскиваются в **композиционном** слое (`crush/internal/agent/coordinator.go:1095` `buildProvider`: Anthropic interleaved-thinking beta-header, ZAI `tool_stream` extra-body), не в транспорте.
- **openai-go / anthropic-sdk-go** Stainless-**генерированные** SDK, в request-слое почти байт-в-байт совпадают; референс «как устроен generic-SDK», не харнесс.
- **Итог:** хэндкрафтового Go-транспорта под наш профиль среди клонов НЕТ. Ближайшее по духу к нам (тонкий транспорт + богатый policy-слой) это как раз наш `internal/llm` + `internal/pipeline`; crush своим сплитом «lib владеет wire + generic-loop, app владеет policy/state» **независимо валидирует** наш `llm`/`pipeline`-шов.
### §5.2 Центральный вердикт (ратифицирован владельцем 23.07)
SDK-паттерн `RequestOption` = `func(*RequestConfig) error` (`openai-go/option/requestoption.go:80-85`; `openai-go/internal/requestconfig/requestconfig.go:102-104`) **непрозрачное замыкание**. Наш детерминизм-контракт **сворачивает resolved wire-shape в job-снапшот** (`capability.go:12-18`), так что правка wire-формы **инвалидирует чекпойнты** (`--resnapshot`), а не тихо false-hit'ит. Замыкание **несериализуемо → нехешируемо → несворачиваемо в снапшот** наша `Capability`-как-**данные** это **не «бедная версия option-паттерна», а правильный дизайн** для детерминизм-критичной системы с фиксированной поверхностью; SDK-паттерн для нас прямо неверный.
Подтверждающий сигнал с другой стороны: **goose под давлением ~50 провайдеров сходится к нашей же позе** data-fицирует только **статический каталог** (endpoint/protocol/model-list/цены), а **все wire-квирки** (param-remap, reasoning-control, finish-reason, usage-shape) держит в **КОДЕ**, кейнутые по 3-значному `engine`-enum и hardcoded-спискам имён (`goose/crates/goose-providers/src/openai.rs:337-421` `PROVIDERS_NEEDING_MAX_TOKENS_REMAP` и др.). Эталон, на который смотрим, приходит **туда же, где мы**: `BuildClient` switch-on-kind + квирки в адаптере.
Показательный артефакт хрупкости generic-SDK: два SDK **расходятся в HTML-escaping** JSON-фолбэка openai `SetEscapeHTML(false)` (`openai-go/internal/requestconfig/requestconfig.go:241`) vs anthropic `SetEscapeHTML(true)` (`anthropic-sdk-go/internal/requestconfig/requestconfig.go:147`). Ровно тот тихий per-provider wire-дивергенс, что байт-детерминизм терпеть не может а мы контролируем тело байт-в-байт через `Capability.applyToBody` (`capability.go:129-172`).
### §5.3 Таблица «взять / не брать»
| Паттерн | Источник (repo/file:line) | Вердикт | Почему (для нашего профиля) |
|---|---|---|---|
| Validation-тест каталога провайдеров | goose `declarative.rs:396-467` (`all_bundled_providers_are_valid`) | **ВЗЯТЬ** (высшая ценность/нулевая цена) | `go test` над `models.yaml`: `key_env` известен, `budget_field`/reasoning-control валидный enum, `min_max_tokens ≤` капов, нет ссылок на необъявленный провайдер. Ловит дрейф ДО платного прогона. |
| loop-detection как stop-условие | crush `internal/agent/loop_detection.go:12-39`, wired `agent.go:1049` | **ВЗЯТЬ** (перетаргетить) | SHA-сигнатура повторяющегося выхода триггер. Перетаргетить на нормализованную сигнатуру перевода-сегмента; **дополняет** `echoMineViolation`, не заменяет. ~40 строк, не зависимость. |
| «никогда не писать тихо $0 usage; оценить + ФЛАГ estimated» | crush `usage_fallback.go` (`EstimatedUsage`); goose `conversation/token_usage.rs:17-22` (`CostSource`) | **ВЗЯТЬ дисциплину** (= Q5#3) | Честную половину (settle резерв-оценки) делаем; не хватает легенды телеметрии. Эвристику `char/4` (`usage_fallback.go:171`) НЕ брать CJK-мина; подставить fertility research/20 (`1.20·cjk+0.39·other`). |
| `preserves_thinking` как именованный per-provider флаг | goose `declarative.rs:147,154-156` | **ВЗЯТЬ опц.** | Централизует наш `echoes_when_thinking_off` + ReasoningNone-no-op в один снапшотимый capability-флаг. |
| Retry-After-Ms / `x-should-retry` | openai-go `requestconfig.go:396-449` / `:383-388` | **ВЗЯТЬ** (уже в §1) | Q2#1 / Q4#3. |
| Тонкий `Provider`-trait + отдельные `ProviderMetadata` | goose `base.rs:388-583` / `:20-47` | **посмотреть** | Хорошая сепарация runtime-контракта от каталог-метаданных; у нас уже близко (LLMClient + models.yaml). Подтверждение, не действие. |
| `RequestOption`/middleware-onion/`apijson`/`param.Opt[T]` | openai-go `option/requestoption.go:80-85`; `internal/apijson/*`; `packages/param/option.go:11-88` | **НЕ БРАТЬ** | Замыкание несворачиваемо в снапшот 5.2); `apijson` = reflection-переписывание `encoding/json` ради 200 эндпоинтов; `Opt[T]` амортизируется на сотнях полей у нас 3-полевое тело + `map[string]any`. |
| Мульти-схемный `Security`-резолвер | openai-go `requestconfig.go:830-906` | **НЕ БРАТЬ** | Один ключ на провайдера; у нас нет Admin/Bearer/OIDC-схем. |
| Интерактивный turn-loop (queue/cancel/IsBusy/drainQueue) | crush `agent.go:305-508` | **НЕ БРАТЬ** | Впрыскивает недетерминизм; batch-волна упорядочена. |
| Permission-сервис (human-in-the-loop) | crush `internal/permission/permission.go` | **НЕ БРАТЬ** | Нет интерактивного апрува. |
| Авто-суммаризация под context-pressure | crush `agent.go:1027-1048` (`StopWhen`) | **НЕ БРАТЬ** | Ломает детерминизм и верность; наш чанкер владеет бюджетом контекста заранее. |
| Live-фетч каталога на старте / posthog-телеметрия | crush `config/provider.go:139-197`; `event/event.go` | **НЕ БРАТЬ** | Сеть на старте = анти-детерминизм; у нас свой ledger. |
| Нондетерминированные источники каталога | goose `declarative.rs:209-233` (`${VAR}`@load), `model.rs:95-128` (OpenRouter-regenerated canonical), live `/v1/models`, n_ctx-probe | **НЕ БРАТЬ** | Каждый вектор тихого false-hit, который наш снапшот специально устраняет. |
| Декларативная finish-map / param-remap в ДАННЫЕ | goose (держит в коде: `formats/openai.rs`, `http_status.rs:181-260`) | **НЕ БРАТЬ** | goose сам НЕ data-fицирует квирки; лишняя снапшот-поверхность. `extra_body`-passthrough уже покрывает «добавить странный параметр». |
| Base-URL-guessing / model-name-regex-роутинг | goose `openai.rs:85-450`; `formats/openai.rs:1531-1537` | **НЕ БРАТЬ** | Существует только потому, что goose принимает произвольный user-`base_url`; наш курированный YAML это обходит. Добавление модели у нас = правка данных, строго гигиеничнее. |
| Debounce/coalesce стрим-дельт | crush `message/message.go:21` | **НЕ БРАТЬ** | Оптимизация стрим-TUI; batch пишет дискретные артефакты. |
### §5.4 Оценка кода (наш vs чужой, без реверанса)
- **Наш.** Purpose-built и факторизован: транспорт 7 файлов, `pipeline` 37 файлов **файл-на-концерн** (банк-майнер расщеплён `miner_detect/alias/emit/palladius/patterns/substrate`; крупнейший `memory.go` 914 строк). Детерминизм-first (Capability в снапшоте), деньги-first (типизированные `HTTPStatusError`/`BilledDecodeError`, reservation-settle). **В ряде мест чище/корректнее SDK для нашей оси**: явный `(T, retryable bool, err)` (`httpllm.go:97`) вместо SDK-шного `noRetryError`-маркера через `errors.As`; billing-типизированные ошибки; снапшот-сериализуемая wire-форма; байт-контроль тела.
- **SDK (openai-go/anthropic-sdk-go).** Компетентная generic-машинерия. Изящно на швах (option-fold, middleware-onion, `Opt[T]` три-стейт present/null/absent, инъекция `HTTPClient` для тестов), тяжело в глубине (`apijson`). Честно: «идиоматичный **вывод генератора**, не рукотворное суждение». Для нас оверинжиниринг по каждой оси генеричности.
- **crush.** Качественный, concurrency-честный; **комментарии выше среднего** (кодируют *почему* и контракт гонок: broker-семантика, eventual-consistency сообщений, race-safety permissions); дженерики к месту (`Broker[T]`, `cache[T]`). Смеллы: **два god-файла** (`agent.go` ~2278 строк, `coordinator.go` ~1500; `agent.Run` одна ~750-строчная функция) тут **мы факторизованы лучше**; `char/4` CJK-мина; stringly-typed `getProviderOptions` (JSON marshalmergeunmarshal`map[string]any`, `coordinator.go:344-390`); квирки размазаны inline-switch'ами.
- **goose.** Идиоматичный, хорошо оттестирован (`test_case`-матрицы), тонкий `Provider`-trait + отдельные метаданные = хорошая сепарация. Смеллы: builder-бойлерплейт (поля по 3 раза, `openai.rs:156-271`), квирки-по-списку-имён, base-URL-guessing «крысиное гнездо». **Главный урок — их cruft:** мёртвый `max_tokens` в 29 JSON (молча съеден serde, нет `deny_unknown_fields`) и инертный `supports_cache_control` показывают: **hand-каталог БЕЗ строгого резолвера копит невалидированные/непотребляемые поля.** Наш «резолвер-который-падает + снапшот-который-форсит-`--resnapshot`» ровно та дисциплина, которой goose лишён. Берём их **validation-тест**, не их **терпимость к полям**.
### §5.5 Рефактор-вердикт
**Транспорт/провайдер-слой — НЕ рефакторить.** Три независимых эталона (generic-SDK, интерактивный агент, 50-провайдерный каталог) валидируют наш тонкий-транспорт + data-каталог шов; их «красоты» написаны под другие задачи, а часть (option-замыкания, live-каталог, авто-суммаризация) **враждебна нашему детерминизму**. Реальный архитектурный долг **не в транспорте, а в `pipeline`** (сломанный телефон 7-слойная целевая, D39), и он уже ратифицирован туда энергия. Из §5 к внедрению только 4 точечные ~40-строчные добавки из §5.3 (validation-тест, loop-detection, CostSource-флаг, `preserves_thinking`), **все DEFER до после пере-прогона**, ни одна не является рефакторингом.
---
## Сообщение оркестратору (кратко)
research/21 готов (черновик, не коммичен лендите вы). Главное: **наш транспорт в базе опережает/вровень со всеми 11** единый retryLoop, 5-мин-кап Retry-After, fail-loud terminal, billed-decode-типизация, served-model биллинг, min_max_tokens-флор, double-count-гард. Значимого рычага против **Mistral-48%** (только concurrency-cap) и **тихого усечения 28k** (стриминг даёт лишь ДЕТЕКЦИЮ, не resume; continuation не стриминг) никто не даёт.
**Самое ценное — Q7-квирки, задевающие пере-прогонные армы (все кандидаты, за вами вендор-сверка):** (1) **Mistral extra_forbidden** mistral-editor-арм упадёт 400 при реплее reasoning-полей; (2) **GLM молча глотает context-overflow** тихое усечение банк-хвоста на glm-5 (сломанный телефон); (3) **GLM finish `sensitive`/`network_error`**; (4) **GLM СОХРАНЯЕТ cache_control** (COGS-рычаг); (5) **kimi reasoning → ОМИТить temperature**, не force:1 (кандидат сменить нашу capability).
**Дёшево-и-подтверждено к внедрению (после пере-прогона):** fractional/ms Retry-After, quota-429terminal, HTTP/2 keepalive, CostSource-маркер, xAI reasoning identity-гард, план native-Gemini-судьи.
**§5 (архитектура, дозаказ владельца 23.07):** транспорт/провайдер-слой **не рефакторить** три эталона (generic-SDK, интерактивный агент, 50-провайдерный каталог) валидируют наш тонкий-транспорт+data-каталог шов; option-замыкание несворачиваемо в снапшот `Capability`-как-данные правильна, а не «бедный option-паттерн»; goose под 50 провайдерами сходится к нашей позе (квирки в коде, данные только статический каталог). К внедрению из §5 4 точечные ~40-строчные добавки (validation-тест `models.yaml`, loop-detection-гард, CostSource-флаг, `preserves_thinking`), все DEFER. Реальный арх-долг в `pipeline`/D39, не в транспорте.
**Процессная заметка (мандат 12.07):** синтез-агенты Q1/Q3 вернули плейсхолдер-заглушки, схема не поймала самопроверка исполнением поймала, Q1/Q3 пере-выведены из целого survey-сырья + независимая верификация (8/8 CONFIRMED). Ещё один датапоинт, что «схема-валидно» «содержательно».