diff --git a/backend/internal/provider/chatgpt/client.go b/backend/internal/provider/chatgpt/client.go index 1af0bf6..51458bd 100644 --- a/backend/internal/provider/chatgpt/client.go +++ b/backend/internal/provider/chatgpt/client.go @@ -898,7 +898,8 @@ func (c *Client) startImageGeneration(ctx context.Context, session tlsclient.Htt // image generation task actually started" signal, so when it is absent (and // nothing was streamed inline) treat the attempt as a transient upstream // failure. That is retryable: a fresh submission reliably engages the pipeline, - // so the pool's same-account retry recovers instead of failing the request. + // so the pool retries the same account a few times and then fails over to + // another account (换号重试) instead of failing the request. if !asyncStarted && len(fileIDs) == 0 && len(sedimentIDs) == 0 { return "", nil, nil, fmt.Errorf("%w: image generation did not start (no async marker)", ErrTemporaryUpstream) } diff --git a/backend/internal/service/v1.go b/backend/internal/service/v1.go index 50a5523..1394890 100644 --- a/backend/internal/service/v1.go +++ b/backend/internal/service/v1.go @@ -2318,8 +2318,11 @@ func (s *V1Service) generateChatGPTImage(ctx context.Context, eventID string, mo return nil, err } - // Round-robin order; same-account retry on transient errors, fail over to the - // next account on auth/quota (see runPoolWithFailover). + // Round-robin order; on a transient upstream error (e.g. "image generation + // did not start (no async marker)") retry the same account a few times then + // FAIL OVER to the next account (tempFailover=true, capped at + // maxTempDeadAccounts) — never mark the account dead. Auth/quota fail over + // immediately (see runPoolWithFailover). return s.runPoolWithFailover(ctx, eventID, "chatgpt", active, "image", func(token model.TokenAccount) ([]byte, error) { data, _, genErr := s.chatgpt.GenerateImage(ctx, token.Value, in.Prompt, modelItem.ID, aspectRatio, resolution, refs) if genErr == nil { @@ -2330,7 +2333,7 @@ func (s *V1Service) generateChatGPTImage(ctx context.Context, eventID string, mo return data, genErr }, func(e error) (bool, bool, bool, bool) { return errors.Is(e, chatgpt.ErrAuth), errors.Is(e, chatgpt.ErrQuotaExhausted), errors.Is(e, chatgpt.ErrTemporaryUpstream), false - }, nil, false) // chatgpt token IS the credential — no cookie to refresh + }, nil, true) // chatgpt token IS the credential — no cookie to refresh; switch accounts on transient errors } // leonardoResetAfter returns when a Leonardo account's daily free tokens renew.