修复 Grok Realtime 预接入切号

This commit is contained in:
IanShaw
2026-08-20 02:32:51 -07:00
parent ad26172b83
commit 611a7c8ed3
2 changed files with 51 additions and 40 deletions
+51 -39
View File
@@ -44,53 +44,65 @@ func (h *OpenAIGatewayHandler) GrokRealtime(c *gin.Context) {
return
}
selection, _, err := h.gatewayService.SelectAccountWithSchedulerForCapability(
c.Request.Context(),
apiKey.GroupID,
"",
"",
"grok-4.5",
nil,
service.OpenAIUpstreamTransportHTTPSSE,
// Grok only advertises chat_completions + media capabilities on HEAD.
service.OpenAIEndpointCapabilityChatCompletions,
false,
false,
false,
service.PlatformGrok,
)
if err != nil || selection == nil || selection.Account == nil {
h.errorResponse(c, http.StatusServiceUnavailable, "api_error", "No available Grok accounts")
return
}
var streamStarted bool
reqLog := requestLogger(c, "handler.openai_gateway.grok_realtime")
release, slotStatus := h.acquireResponsesAccountSlot(c, apiKey.GroupID, "", selection, true, &streamStarted, reqLog)
if slotStatus != openAISlotAcquireOK {
return
}
defer release()
token, _, err := h.gatewayService.GetRequestCredential(c.Request.Context(), c, selection.Account)
if err != nil {
h.errorResponse(c, http.StatusBadGateway, "upstream_error", "Grok credential unavailable")
return
}
model := c.Query("model")
if strings.TrimSpace(model) == "" {
model = "grok-voice-latest"
}
// Complete the upstream handshake before sending HTTP 101. This keeps
// pre-accept failures representable as normal JSON errors to the client.
probeCtx, cancelProbe := context.WithTimeout(c.Request.Context(), 15*time.Second)
probeErr := h.gatewayService.ProbeGrokRealtime(probeCtx, selection.Account, token, model)
cancelProbe()
if probeErr != nil {
reqLog.Warn("grok_realtime.pre_accept_failed", zap.Error(probeErr))
// Keep the HTTP response uncommitted while selecting and probing an account.
// Realtime is not an HTTP streaming response; using reqStream=true here would
// let the wait queue flush an SSE ping before the WebSocket handshake succeeds.
failed := map[int64]struct{}{}
var selection *service.AccountSelectionResult
var release func()
var token string
for attempts := 0; attempts < 4; attempts++ {
candidate, _, selectErr := h.gatewayService.SelectAccountWithSchedulerForCapability(
c.Request.Context(), apiKey.GroupID, "", "", "grok-4.6", failed,
service.OpenAIUpstreamTransportHTTPSSE,
service.OpenAIEndpointCapabilityChatCompletions,
false, false, false, service.PlatformGrok,
)
if selectErr != nil || candidate == nil || candidate.Account == nil {
break
}
account := candidate.Account
var streamStarted bool
var slotStatus openAISlotAcquireResult
release, slotStatus = h.acquireResponsesAccountSlot(c, apiKey.GroupID, "", candidate, false, &streamStarted, reqLog)
if slotStatus != openAISlotAcquireOK {
if slotStatus == openAISlotAcquireFailed {
return
}
failed[account.ID] = struct{}{}
continue
}
var credErr error
token, _, credErr = h.gatewayService.GetRequestCredential(c.Request.Context(), c, account)
if credErr != nil {
release()
release = nil
failed[account.ID] = struct{}{}
continue
}
probeCtx, cancelProbe := context.WithTimeout(c.Request.Context(), 15*time.Second)
probeErr := h.gatewayService.ProbeGrokRealtime(probeCtx, account, token, model)
cancelProbe()
if probeErr != nil {
reqLog.Warn("grok_realtime.pre_accept_failed", zap.Int64("account_id", account.ID), zap.Error(probeErr))
release()
release = nil
failed[account.ID] = struct{}{}
continue
}
selection = candidate
break
}
if selection == nil || selection.Account == nil || release == nil {
h.errorResponse(c, http.StatusBadGateway, "upstream_error", "Grok realtime upstream unavailable")
return
}
defer release()
conn, err := coderws.Accept(c.Writer, c.Request, &coderws.AcceptOptions{CompressionMode: coderws.CompressionContextTakeover})
if err != nil {
-1
View File
@@ -63,7 +63,6 @@ func (s *OpenAIGatewayService) ForwardGrokVoice(ctx context.Context, c *gin.Cont
}
upstreamCtx, release := detachUpstreamContext(ctx)
defer release()
upstreamCtx = WithHTTPUpstreamProfile(upstreamCtx, HTTPUpstreamProfileGrok)
method := http.MethodPost
if c != nil && c.Request != nil && strings.TrimSpace(c.Request.Method) != "" {
method = c.Request.Method