fix: 4xx (400/401/404/...) теперь триггерит failover вместо пробрасывания клиенту
Build LLM Proxy / Build and push (release) Successful in 59s
Build LLM Proxy / Build and push (release) Successful in 59s
Раньше 4xx-ответы upstream'а (напр. 400 'Model is unavailable' от opencode.ai) передавались клиенту как есть — следующие upstream'ы в пуле не пробовались. В твоей модели assistant после deepseek-v4-flash-opencode с 400 оставались ещё три upstream'а (minimax-m3, minimax-m3-2, deepseek-v4-flash), которые могли бы ответить — но прокси молча возвращал 400 и сдавался. Теперь любой 4xx — failover-триггер (как 5xx/429/402), тело ошибки логируется, но клиенту не возвращается. Если все upstream'ы 4xx — клиент получит 503 'all upstreams failed', а не 400 от самого слабого звена пула. Memory 2733 обновлена.
This commit is contained in:
@@ -276,27 +276,34 @@ private suspend fun handleChat(
|
||||
setBody(forwarded.toString())
|
||||
}.execute { resp ->
|
||||
upstreamStatus = resp.status.value
|
||||
if (upstreamStatus in 400..599) {
|
||||
// 4xx/5xx — upstream не смог обработать запрос: 5xx/429/402 — сбой
|
||||
// провайдера (учёт в backoff); 400/401/404/etc. — ошибка либо
|
||||
// запроса, либо состояния апстрима (например, "Model is unavailable"),
|
||||
// но в обоих случаях следующий upstream в пуле может сработать, и
|
||||
// пробуем его. Тело ошибки логируем, но клиенту не возвращаем —
|
||||
// иначе пользователь увидит 400 от самого слабого звена пула,
|
||||
// хотя другие upstream'ы могли бы ответить.
|
||||
val errorBody = runCatching { resp.body<String>() }.getOrDefault("")
|
||||
if (upstreamStatus >= 500 || upstreamStatus == 429 || upstreamStatus == 402) {
|
||||
val rest = backoff.recordFailure(up)
|
||||
metrics.record(modelName, up, statusClass(upstreamStatus))
|
||||
log.warn {
|
||||
"[llm-proxy] model=$modelName upstream=${up.id} вернул $upstreamStatus → фейловер" +
|
||||
"[llm-proxy] model=$modelName upstream=${up.id} вернул $upstreamStatus " +
|
||||
"errorBody=${errorBody.take(500)} → фейловер" +
|
||||
(rest?.let { " (backoff: отдых ${it.toIsoString()})" } ?: "")
|
||||
}
|
||||
} else {
|
||||
metrics.record(modelName, up, statusClass(upstreamStatus))
|
||||
log.warn {
|
||||
"[llm-proxy] model=$modelName upstream=${up.id} вернул $upstreamStatus " +
|
||||
"errorBody=${errorBody.take(500)} → фейловер"
|
||||
}
|
||||
}
|
||||
failed.add(up.id)
|
||||
failover = true
|
||||
return@execute
|
||||
}
|
||||
if (upstreamStatus in 400..499) {
|
||||
// 4xx — JSON-тело, не стрим: читаем безопасно и отдаём клиенту как есть.
|
||||
val errorBody = runCatching { resp.body<String>() }.getOrDefault("")
|
||||
log.warn { "[llm-proxy] chat model=$modelName upstream=${up.id} вернул $upstreamStatus errorBody=${errorBody.take(500)}" }
|
||||
metrics.record(modelName, up, statusClass(upstreamStatus))
|
||||
val ct = resp.headers["Content-Type"] ?: "application/json"
|
||||
call.respondText(errorBody, ContentType.parse(ct), HttpStatusCode.fromValue(upstreamStatus))
|
||||
responded = true
|
||||
return@execute
|
||||
}
|
||||
responded = true
|
||||
backoff.recordSuccess(up)
|
||||
if (clientWantsStream) {
|
||||
|
||||
Reference in New Issue
Block a user