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())
|
setBody(forwarded.toString())
|
||||||
}.execute { resp ->
|
}.execute { resp ->
|
||||||
upstreamStatus = resp.status.value
|
upstreamStatus = resp.status.value
|
||||||
if (upstreamStatus >= 500 || upstreamStatus == 429 || upstreamStatus == 402) {
|
if (upstreamStatus in 400..599) {
|
||||||
val rest = backoff.recordFailure(up)
|
// 4xx/5xx — upstream не смог обработать запрос: 5xx/429/402 — сбой
|
||||||
metrics.record(modelName, up, statusClass(upstreamStatus))
|
// провайдера (учёт в backoff); 400/401/404/etc. — ошибка либо
|
||||||
log.warn {
|
// запроса, либо состояния апстрима (например, "Model is unavailable"),
|
||||||
"[llm-proxy] model=$modelName upstream=${up.id} вернул $upstreamStatus → фейловер" +
|
// но в обоих случаях следующий upstream в пуле может сработать, и
|
||||||
(rest?.let { " (backoff: отдых ${it.toIsoString()})" } ?: "")
|
// пробуем его. Тело ошибки логируем, но клиенту не возвращаем —
|
||||||
|
// иначе пользователь увидит 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 " +
|
||||||
|
"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)
|
failed.add(up.id)
|
||||||
failover = true
|
failover = true
|
||||||
return@execute
|
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
|
responded = true
|
||||||
backoff.recordSuccess(up)
|
backoff.recordSuccess(up)
|
||||||
if (clientWantsStream) {
|
if (clientWantsStream) {
|
||||||
|
|||||||
Reference in New Issue
Block a user