From adaffba885b3ca00e41136c87738b94a6e5a999d Mon Sep 17 00:00:00 2001 From: subochev Date: Sun, 13 Sep 2026 20:33:28 +0300 Subject: [PATCH] =?UTF-8?q?feat:=20402=20(insufficient=20balance=20/=20?= =?UTF-8?q?=D0=BB=D0=B8=D0=BC=D0=B8=D1=82=20=D1=82=D0=BE=D0=BA=D0=B5=D0=BD?= =?UTF-8?q?=D0=BE=D0=B2)=20=E2=80=94=20=D1=84=D0=B5=D0=B9=D0=BB=D0=BE?= =?UTF-8?q?=D0=B2=D0=B5=D1=80=20=D0=BD=D0=B0=20=D1=81=D0=BB=D0=B5=D0=B4?= =?UTF-8?q?=D1=83=D1=8E=D1=89=D0=B8=D0=B9=20=D0=B0=D0=BF=D1=81=D1=82=D1=80?= =?UTF-8?q?=D0=B8=D0=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 402 от провайдера — ошибка аккаунта (баланс/лимит токенов), а не запроса: следующий апстрим в списке модели может быть платёжеспособен. Теперь при 5xx/429/402 освобождаем слот и пробуем следующего свободного; остальные 4xx продолжаем отдавать клиенту как есть. Обновлён CONFIG.md (маршрутизация, лог-секции). Ранее этот правка терялась (3155065 не сохранился) — восстанавливаю. --- CONFIG.md | 8 +++++--- src/commonMain/kotlin/pw/binom/llmproxy/Main.kt | 2 +- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/CONFIG.md b/CONFIG.md index 94306b8..d5568ea 100644 --- a/CONFIG.md +++ b/CONFIG.md @@ -41,9 +41,11 @@ запросов сейчас идёт на каждый апстрим, и по этим счётчикам решаем, брать запрос или нет. Мы **не полагаемся** на `503`/`429` от самого апстрима, чтобы узнать, что он перегружен: такой ответ от провайдера — это уже сбой, а не способ -управления нагрузкой. Если выбранный апстрим всё же вернул `5xx`/`429`, мы +управления нагрузкой. Если выбранный апстрим всё же вернул `5xx`/`429`/`402`, мы освобождаем его слот и, при наличии, пробуем **следующий свободный** из списка модели (фейловер); если свободных не осталось — отдаём `503` от прокси. +`402` (insufficient balance / исчерпан лимит токенов) — ошибка аккаунта +провайдера, но фейловер имеет смысл: следующий апстрим может быть платёжеспособен. ## Формат файла @@ -454,7 +456,7 @@ fun release(u: UpstreamConf) = active.getValue(u.id).decrementAndGet() `upstream.patch` → `model.patch`), каждый через `merge(...)`; слои с `patch == null` пропускаем. Итог — финальное тело запроса; - шлём на `provider.url + /chat/completions` с `Authorization: Bearer provider.key`; - - при ответе `5xx`/`429` от провайдера — `release(upstream)` и переходим к + - при ответе `5xx`/`429`/`402` от провайдера — `release(upstream)` и переходим к следующему свободному апстриму из списка (фейловер); исчерпали список — отдаём `503` (`all upstreams failed`); - при успехе/отмене клиента — `release(upstream)` в `finally` и выходим. @@ -507,7 +509,7 @@ fun release(u: UpstreamConf) = active.getValue(u.id).decrementAndGet() свободному `upstream=`. - **Завершение** (успех/ошибка/отмена клиента): длительность, статус апстрима, освобождён слот `active[id]=N/limit`. -- **Ошибка апстрима** (не `5xx`/`429`, а сетевая/таймаут): как сейчас — лог с +- **Ошибка апстрима** (не `5xx`/`429`/`402`, а сетевая/таймаут): как сейчас — лог с сообщением. Формат строки лога — один префикс `[llm-proxy]`, как сейчас, чтобы не ломать diff --git a/src/commonMain/kotlin/pw/binom/llmproxy/Main.kt b/src/commonMain/kotlin/pw/binom/llmproxy/Main.kt index 5c39058..c2bd32a 100644 --- a/src/commonMain/kotlin/pw/binom/llmproxy/Main.kt +++ b/src/commonMain/kotlin/pw/binom/llmproxy/Main.kt @@ -229,7 +229,7 @@ private suspend fun handleChat( setBody(forwarded.toString()) }.execute { resp -> upstreamStatus = resp.status.value - if (upstreamStatus >= 500 || upstreamStatus == 429) { + if (upstreamStatus >= 500 || upstreamStatus == 429 || upstreamStatus == 402) { log.warn { "[llm-proxy] model=$modelName upstream=${up.id} вернул $upstreamStatus → фейловер" } failed.add(up.id) failover = true