75 KiB
research/21 — Обзор LLM-транспорта чужих харнессов
РЕВЬЮ-ШАПКА ОРКЕСТРАТОРА (лендинг 23.07.2026). Спот-чек адверсариальный, author≠reviewer: 7/7 несущих цитат о НАШЕМ коде подтверждены по file:line (
httpllm.gojitter/клиент/429/parseRetryAfter,provider_openai.goadditive,llm.govojo) + 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-shotsystem+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-пункты 1–6/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 Q1–Q6) ∥ 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. Топ-рекомендации (ранжированы по ожидаемому импакту)
Формат: приоритет · [вердикт] · рекомендация · какая метрика двинется и на сколько · гейт. «Держим как есть» с внешним подтверждением — тоже вывод (мы там ведём).
Надёжность / тихий провал (высший приоритет — задевает пере-прогонные армы)
-
[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). -
[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 → отдельный terminalContextOverflow. Гейт: одна-харнессовая примета (opencode-комментарий, не wire-трасса) → вендор-сверка z.ai обязательна перед абсорбцией; порог оценщика — консервативный. Ev:opencode/packages/opencode/src/provider/error.ts:30-31; gooseis_context_length_exceeded_messagegoose/crates/goose-providers/src/http_status.rs:181-249. -
[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. -
[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: opencodepackages/llm/src/route/executor.ts:242-245+schema/errors.ts:87-96; goosehttp_status.rs:181-249(402→CreditsExhausted). -
[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-buildshared_http.rs:86-96(http2 keepalive 15s/5s); openai-godefault_http_client.go:10-14(ResponseHeaderTimeout). -
[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. -
[VERIFIED, NEW] xAI:
x-should-retry:falseв ответе форсит FATAL поверх статуса. Официальный grok-харнесс чтит headerx-should-retryкак авторитет над кодом: провайдер может пометить content-caused 5xx неретраебельным (экономит доMaxAttempts−1ПЛАТНЫХ попыток). Три реализации (openai-go, anthropic-sdk-go, grok-build) сходятся. Дёшево, безвредно при отсутствии header (наши OpenAI-compat ноги в основном его не шлют; xAI/OpenAI — шлют). Метрика: $ / надёжность — до 2 платных ретраев сэкономлено на server-marked-fatal 5xx; no-op при отсутствии. Ev: openai-gointernal/requestconfig/requestconfig.go:383-388; anthropic-sdk-gointernal/requestconfig/requestconfig.go:262-267; grok-buildretry.rs:188-197. -
[VERIFIED, NEW] Локаль/ollama: HTTP 200 с ошибкой В ТЕЛЕ стрима. ollama возвращает 200, даже когда стрим несёт
error-поле — успех/провал судится по событию, не по коду. Наш local-адаптер/liveness (WSL2-стенд) должен парсить тело, не только статус. Метрика: надёжность local-fallback — ложный «жив» при битой локали. Ev: codexcodex-rs/ollama/src/client.rs:190-197,227-235.
Деньги / точность биллинга
-
[PARTIAL→реально] xAI reasoning: identity-гард против латентного 30–44% 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% сегодня, но кап латентного ~30–44% двойного счёта (та самая 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-buildxai-chat-state/src/usage.rs:44-61(читает reasoning как subset — но это ДРУГОЙ эндпоинт, xAI-native, не Chat Completions). -
[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: gooseconversation/token_usage.rs:17-22; crushinternal/agent/agent.go:1896-1927. -
[CONFIRMED] План: судья Gemini → нативный
generateContent. На OpenAI-compat-слое Gemini роняет thoughts-поле → мы деривим reasoning вычитанием (total−prompt−completion). Нативный эндпоинт отдаёт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: litellmvertex_and_google_ai_studio_gemini.py:1729-1745,1908-1944; opencodeprotocols/gemini.ts:338-361.
$ / cache (вопрос №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).
Мелкое / отложенное (полнота)
-
[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: opencoderoute/executor.ts:93-95; openai-gorequestconfig.go:396-449; anthropic-sdk-gorequestconfig.go:275-328. -
[PARTIAL] Проба (не билд!): что DeepSeek/GLM/Mistral реально шлют на 429.
obs.LogLLMExchangeуже пишет headers+body — проверить в пере-прогоне, есть лиx-ratelimit-{limit,remaining,reset}/body-retry-поле. opencode парсит эти окна, но тратит их на observability — рычаг «пейсить ДО 429» никто не построил. Не строить пейсер сейчас — решать по данным (правило двух направлений). Ev: opencoderoute/executor.ts:112-148; litellmrouter.py:2823-2847(pre-call RPM-чек внутри семафора — форма «пейсить до 429», если есть budget-header). -
[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%→~0–5% приmax_concurrencyпод потолком тира; ноль кода, ноль COGS. Гейт: per-tier калибровка; пере-замер при mistral-editor-арме. Ev: litellmrouter.py:2823-2847; gooseopenai.rs:337-348(Mistral — только max_tokens-remap, не rate). -
[PARTIAL, GATED] HTTP/1.1-escape на последней попытке — диагностика тихого усечения. grok-build построил
send_with_retry_escaping_pool(xai-grok-http/src/lib.rs:432-475) ровно чтобы уйти с «отравленного» HTTP/2-пула, и их truncation-регрессия — в том же модуле → правдоподобная причина нашего тихого усечения на 2–8k. Но корень НЕ подтверждён (может быть finish=length, который мы уже лечим) → CLAUDE.md запрещает гадать: гейтить как эксперимент с замером h2-vs-forced-h1, не лендить дефолтом. Метрика: truncation-rate 2–8k (baseline неизвестен — потенциально крупнейшая тихая утечка качества). Ev: grok-buildretry.rs:231-246. -
[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 (стриминг его НЕ даёт). Метрика: $ — экономит ре-билл уже-эмитнутого префикса (~40–60% editor-генерации) на событие усечения. Гейт: HIGH-риск для детерминизма (мульти-хоп, golden/snapshot; наш src→dst-блок наивный сплайс может побить; Gemini/GLM prefix-continuation-семантика различна) → per-modelsupports_assistant_prefill+ адверсариальный golden-ревью; скорее DEFER. -
[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 показал баг «сообщение дублируется 30–50×» на 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*60−5с под 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=prompt−cached−write≥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 2–8k выход ждёт ~2–4 мин, а её жёсткий non-streaming кап (Opus 8192) ≥ всего нашего диапазона → attempt_s=240 внутри сертифицированного вендором безопасного non-streaming конверта.
Берём/не берём. Держим non-streaming дефолтом + StreamingLLMClient-отсрочку, но с явным trip-wire: стриминг ТОЛЬКО для длинного editor-арма, ТОЛЬКО если тихое усечение сохранится на верху 2–8k на вебновелл-срезе, и 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;_loaddrift-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. Особенность: OpenRouterUsage.Costoverride, 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.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-ярусной свежестью live→disk→embedded-at-release) +openai/openai-go/v3. Своё = policy/orchestration/state; квирки впрыскиваются в композиционном слое (crush/internal/agent/coordinator.go:1095buildProvider: Anthropic interleaved-thinking beta-header, ZAItool_streamextra-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 файлов,
pipeline37 файлов файл-на-концерн (банк-майнер расщеплёнminer_detect/alias/emit/palladius/patterns/substrate; крупнейшийmemory.go914 строк). Детерминизм-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/4CJK-мина; stringly-typedgetProviderOptions(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) и тихого усечения 2–8k (стриминг даёт лишь ДЕТЕКЦИЮ, не 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). Ещё один датапоинт, что «схема-валидно» ≠ «содержательно».