mirror of
https://gitcode.com/JianFeeeee/ModelRouter.git
synced 2026-09-20 17:07:59 +00:00
feat(gateway): collapse multi-tier failure details into short client message
Clients saw the full per-tier chain error including upstream HTML WAF pages and JSON quota payloads. The response now carries one capped one-line reason per tier (quota/cooling skips preserved); full detail remains in rec.Err / stats API and is logged server-side.
This commit is contained in:
@ -5,6 +5,7 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
@ -523,6 +524,40 @@ func upstreamErrStatus(err error) int {
|
|||||||
return http.StatusBadGateway
|
return http.StatusBadGateway
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// clientUpstreamErr collapses upstream failure details into a short message
|
||||||
|
// for the client: per-tier bodies (WAF HTML pages, quota payloads, ...) stay
|
||||||
|
// in rec.Err / the stats API and the server log instead of the response.
|
||||||
|
// Per-tier one-line reasons are kept (quota/cooling skips carry no error body
|
||||||
|
// and are the actionable part); each is capped so HTML dumps can't leak.
|
||||||
|
func clientUpstreamErr(err error) string {
|
||||||
|
log.Printf("[gateway] upstream failure surfaced to client: %v", err)
|
||||||
|
var ce *scheduler.ChainErr
|
||||||
|
if errors.As(err, &ce) {
|
||||||
|
parts := make([]string, 0, len(ce.Tiers)+len(ce.Skipped))
|
||||||
|
for _, t := range ce.Tiers {
|
||||||
|
parts = append(parts, oneLine(fmt.Sprintf("%s/%s: %v", t.Source, t.Model, t.Err), 80))
|
||||||
|
}
|
||||||
|
for _, sk := range ce.Skipped {
|
||||||
|
parts = append(parts, oneLine(sk, 80))
|
||||||
|
}
|
||||||
|
msg := strings.Join(parts, "; ")
|
||||||
|
if len(msg) > 300 {
|
||||||
|
msg = msg[:300] + "..."
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("all %d auto providers failed: %s", len(parts), msg)
|
||||||
|
}
|
||||||
|
return oneLine(err.Error(), 160)
|
||||||
|
}
|
||||||
|
|
||||||
|
// oneLine flattens an error string to a single line capped at n chars.
|
||||||
|
func oneLine(s string, n int) string {
|
||||||
|
s = strings.Join(strings.Fields(s), " ")
|
||||||
|
if len(s) > n {
|
||||||
|
s = s[:n] + "..."
|
||||||
|
}
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
func (g *Gateway) singleChat(w http.ResponseWriter, ctx context.Context, cands []*provider.Provider, req *types.ChatRequest, effective string, rec *Req) {
|
func (g *Gateway) singleChat(w http.ResponseWriter, ctx context.Context, cands []*provider.Provider, req *types.ChatRequest, effective string, rec *Req) {
|
||||||
rec.LatMs = 0
|
rec.LatMs = 0
|
||||||
t0 := time.Now()
|
t0 := time.Now()
|
||||||
@ -533,7 +568,7 @@ func (g *Gateway) singleChat(w http.ResponseWriter, ctx context.Context, cands [
|
|||||||
rec.Status = upstreamErrStatus(err)
|
rec.Status = upstreamErrStatus(err)
|
||||||
rec.Err = err.Error()
|
rec.Err = err.Error()
|
||||||
g.writeRec(rec)
|
g.writeRec(rec)
|
||||||
writeError(w, rec.Status, "upstream_error", err.Error())
|
writeError(w, rec.Status, "upstream_error", clientUpstreamErr(err))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
rec.OK = true
|
rec.OK = true
|
||||||
@ -620,7 +655,7 @@ func (g *Gateway) streamChat(w http.ResponseWriter, ctx context.Context, cands [
|
|||||||
rec.OK = false
|
rec.OK = false
|
||||||
rec.Status = upstreamErrStatus(err)
|
rec.Status = upstreamErrStatus(err)
|
||||||
rec.Err = err.Error()
|
rec.Err = err.Error()
|
||||||
writeError(w, rec.Status, "upstream_error", err.Error())
|
writeError(w, rec.Status, "upstream_error", clientUpstreamErr(err))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if usedModel != "" {
|
if usedModel != "" {
|
||||||
@ -741,7 +776,7 @@ func (g *Gateway) singleChatAuto(w http.ResponseWriter, ctx context.Context, cha
|
|||||||
rec.Model = ce.Tiers[0].Model
|
rec.Model = ce.Tiers[0].Model
|
||||||
}
|
}
|
||||||
g.writeRec(rec)
|
g.writeRec(rec)
|
||||||
writeError(w, rec.Status, "upstream_error", err.Error())
|
writeError(w, rec.Status, "upstream_error", clientUpstreamErr(err))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
rec.OK = true
|
rec.OK = true
|
||||||
@ -799,7 +834,7 @@ func (g *Gateway) streamChatAuto(w http.ResponseWriter, ctx context.Context, cha
|
|||||||
rec.Source = ce.Tiers[0].Source
|
rec.Source = ce.Tiers[0].Source
|
||||||
rec.Model = ce.Tiers[0].Model
|
rec.Model = ce.Tiers[0].Model
|
||||||
}
|
}
|
||||||
writeError(w, rec.Status, "upstream_error", err.Error())
|
writeError(w, rec.Status, "upstream_error", clientUpstreamErr(err))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if usedModel != "" {
|
if usedModel != "" {
|
||||||
@ -946,7 +981,7 @@ func (g *Gateway) handleImage(w http.ResponseWriter, r *http.Request) {
|
|||||||
rec.Status = upstreamErrStatus(err)
|
rec.Status = upstreamErrStatus(err)
|
||||||
rec.Err = err.Error()
|
rec.Err = err.Error()
|
||||||
g.writeRec(rec)
|
g.writeRec(rec)
|
||||||
writeError(w, rec.Status, "upstream_error", err.Error())
|
writeError(w, rec.Status, "upstream_error", clientUpstreamErr(err))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if usedSrc != "" {
|
if usedSrc != "" {
|
||||||
|
|||||||
@ -139,7 +139,7 @@ func TestChatAutoChain503Summary(t *testing.T) {
|
|||||||
if rr.Code != http.StatusServiceUnavailable {
|
if rr.Code != http.StatusServiceUnavailable {
|
||||||
t.Fatalf("status=%d body=%s", rr.Code, rr.Body.String())
|
t.Fatalf("status=%d body=%s", rr.Code, rr.Body.String())
|
||||||
}
|
}
|
||||||
if !strings.Contains(rr.Body.String(), "all auto tiers failed") ||
|
if !strings.Contains(rr.Body.String(), "auto providers failed") ||
|
||||||
!strings.Contains(rr.Body.String(), "a/a-m") ||
|
!strings.Contains(rr.Body.String(), "a/a-m") ||
|
||||||
!strings.Contains(rr.Body.String(), "b/b-m") {
|
!strings.Contains(rr.Body.String(), "b/b-m") {
|
||||||
t.Fatalf("503 must summarize every tier, body=%s", rr.Body.String())
|
t.Fatalf("503 must summarize every tier, body=%s", rr.Body.String())
|
||||||
|
|||||||
Reference in New Issue
Block a user