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

75 KiB
Raw Blame History

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.

Деньги / точность биллинга

  1. [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).

  2. [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.

  3. [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 — но импакт скромнее, чем звучит)

  1. [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).

Мелкое / отложенное (полнота)

  1. [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.

  2. [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).

  3. [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).

  4. [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.

  5. [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.

  6. [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<x≤60s. crush/cline — делегируют SDK.

Берём/не берём. Берём: quota-429→terminal (#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. Три независимых адверсариальных чтения (author≠reviewer, дозаказаны 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.yamlCapability-резолвер, свёрнутый в 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-ярусной свежестью live→disk→embedded-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 marshal→merge→unmarshal→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-429→terminal, 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). Ещё один датапоинт, что «схема-валидно» ≠ «содержательно».