完善 Grok Realtime 与默认映射测试

This commit is contained in:
IanShaw
2026-08-20 04:26:24 -07:00
parent f7145c7508
commit 3243983b72
7 changed files with 32 additions and 17 deletions
+14 -3
View File
@@ -57,9 +57,15 @@ func (h *OpenAIGatewayHandler) GrokRealtime(c *gin.Context) {
var release func()
var token string
var upstream *service.GrokRealtimeUpstream
var candidateSeen bool
for attempts := 0; attempts < 4; attempts++ {
// Realtime's voice model is not a text-model capability. Passing a
// concrete text model here would reject accounts mapped only to an
// older/default text model before the upstream handshake can decide.
// An empty requested model keeps account selection capability-based;
// the actual voice model remains in the upstream WS query below.
candidate, _, selectErr := h.gatewayService.SelectAccountWithSchedulerForCapability(
c.Request.Context(), apiKey.GroupID, "", "", "grok-4.6", failed,
c.Request.Context(), apiKey.GroupID, "", "", "", failed,
service.OpenAIUpstreamTransportHTTPSSE,
service.OpenAIEndpointCapabilityChatCompletions,
false, false, false, service.PlatformGrok,
@@ -67,6 +73,7 @@ func (h *OpenAIGatewayHandler) GrokRealtime(c *gin.Context) {
if selectErr != nil || candidate == nil || candidate.Account == nil {
break
}
candidateSeen = true
account := candidate.Account
var streamStarted bool
var slotStatus openAISlotAcquireResult
@@ -86,7 +93,7 @@ func (h *OpenAIGatewayHandler) GrokRealtime(c *gin.Context) {
failed[account.ID] = struct{}{}
continue
}
probeCtx, cancelProbe := context.WithTimeout(c.Request.Context(), 15*time.Second)
probeCtx, cancelProbe := context.WithTimeout(c.Request.Context(), service.DefaultGrokRealtimeDialTimeout)
candidateUpstream, openErr := h.gatewayService.OpenGrokRealtime(probeCtx, account, token, model)
cancelProbe()
if openErr != nil {
@@ -100,7 +107,11 @@ func (h *OpenAIGatewayHandler) GrokRealtime(c *gin.Context) {
break
}
if selection == nil || selection.Account == nil || release == nil || upstream == nil {
h.errorResponse(c, http.StatusBadGateway, "upstream_error", "Grok realtime upstream unavailable")
if !candidateSeen {
h.errorResponse(c, http.StatusServiceUnavailable, "api_error", "No available Grok accounts")
} else {
h.errorResponse(c, http.StatusBadGateway, "upstream_error", "Grok realtime upstream unavailable")
}
return
}
defer release()
@@ -672,7 +672,7 @@ func TestResolveOpenAIMessagesDispatchMappedModel(t *testing.T) {
Platform: service.PlatformGrok,
},
}
require.Equal(t, "grok-4.5", resolveOpenAIMessagesDispatchMappedModel(nil, apiKey, "claude-sonnet-4-5"))
require.Equal(t, "grok-4.6", resolveOpenAIMessagesDispatchMappedModel(nil, apiKey, "claude-sonnet-4-5"))
require.Empty(t, resolveOpenAIMessagesDispatchMappedModel(nil, apiKey, "grok"))
})
@@ -101,7 +101,7 @@ const (
AccountTestModeGrokRealtime = "realtime"
defaultGrokRealtimeTestModel = "grok-voice-latest"
grokRealtimeProbeTimeout = 12 * time.Second
grokRealtimeProbeTimeout = DefaultGrokRealtimeDialTimeout
)
// isOpenAIImageModel checks if the model is an OpenAI image generation model (e.g. gpt-image-2).
+4
View File
@@ -16,6 +16,10 @@ import (
"github.com/tidwall/gjson"
)
// DefaultGrokRealtimeDialTimeout bounds the pre-accept upstream handshake.
// The timeout only covers dialing; an established session is not interrupted.
const DefaultGrokRealtimeDialTimeout = 12 * time.Second
// supportedGrokVoiceHTTPEndpoints are xAI Voice HTTP paths we forward as-is.
var supportedGrokVoiceHTTPEndpoints = map[string]struct{}{
"tts": {},
@@ -251,7 +251,7 @@ func TestForwardGrokChatViaResponsesNonStreamingCachesAndReturnsChat(t *testing.
require.NotNil(t, result)
require.Equal(t, xai.DefaultCLIBaseURL+"/responses", upstream.lastReq.URL.String())
require.Equal(t, grokChatResponsesEndpoint, result.UpstreamEndpoint)
require.Equal(t, "grok-4.5", result.UpstreamModel)
require.Equal(t, "grok-4.6", result.UpstreamModel)
require.Equal(t, 9908, result.Usage.InputTokens)
require.Equal(t, 12, result.Usage.OutputTokens)
require.Equal(t, 9856, result.Usage.CacheReadInputTokens)
@@ -352,7 +352,7 @@ func TestForwardGrokChatViaResponsesCodeBuddyUsesStableConversationHeader(t *tes
c.Request.Header.Set("X-Request-ID", "generic-"+tt.requestID)
c.Set("api_key", &APIKey{ID: 7111})
identity := resolveGrokCacheIdentity(c, tt.body, "", "grok-4.5")
identity := resolveGrokCacheIdentity(c, tt.body, "", "grok-4.6")
require.NotEmpty(t, identity)
if index == 0 {
stableIdentity = identity
@@ -406,8 +406,8 @@ func TestForwardGrokChatViaResponsesTraeToolHistoryKeepsCacheRoute(t *testing.T)
accountRepo: repo,
}
firstTurnIdentity := resolveGrokCacheIdentity(c, firstTurnBody, "", "grok-4.5")
extendedTurnIdentity := resolveGrokCacheIdentity(c, body, "", "grok-4.5")
firstTurnIdentity := resolveGrokCacheIdentity(c, firstTurnBody, "", "grok-4.6")
extendedTurnIdentity := resolveGrokCacheIdentity(c, body, "", "grok-4.6")
require.NotEmpty(t, firstTurnIdentity)
require.Equal(t, firstTurnIdentity, extendedTurnIdentity)
@@ -463,8 +463,8 @@ func TestForwardGrokChatViaResponsesTraeCompatibilityFieldsKeepCacheRoute(t *tes
accountRepo: repo,
}
firstTurnIdentity := resolveGrokCacheIdentity(c, firstTurnBody, "", "grok-4.5")
extendedTurnIdentity := resolveGrokCacheIdentity(c, body, "", "grok-4.5")
firstTurnIdentity := resolveGrokCacheIdentity(c, firstTurnBody, "", "grok-4.6")
extendedTurnIdentity := resolveGrokCacheIdentity(c, body, "", "grok-4.6")
require.NotEmpty(t, firstTurnIdentity)
require.Equal(t, firstTurnIdentity, extendedTurnIdentity)
@@ -534,7 +534,7 @@ func TestForwardGrokChatRuntimeGateFallsBackToRaw(t *testing.T) {
mappedModel string
wantUpstream string
}{
{name: "missing cache identity", wantUpstream: "grok-4.5"},
{name: "missing cache identity", wantUpstream: "grok-4.6"},
{name: "non cache capable mapped model", setAPIKey: true, mappedModel: "grok-4.3", wantUpstream: "grok-4.3"},
}
@@ -1791,7 +1791,7 @@ func TestForwardAsChatCompletionsForGrokStopFallsBackToXAIChatCompletions(t *tes
require.Equal(t, "Bearer access-token", upstream.lastReq.Header.Get("Authorization"))
require.NotEmpty(t, upstream.lastReq.Header.Get(grokConversationIDHeader))
require.NotEqual(t, "raw-client-cache-key", upstream.lastReq.Header.Get(grokConversationIDHeader))
require.Equal(t, "grok-4.5", gjson.GetBytes(upstream.lastBody, "model").String())
require.Equal(t, "grok-4.6", gjson.GetBytes(upstream.lastBody, "model").String())
require.False(t, gjson.GetBytes(upstream.lastBody, "prompt_cache_key").Exists())
require.Equal(t, "grok", result.Model)
require.Equal(t, "grok-4.5", result.UpstreamModel)
@@ -1848,7 +1848,7 @@ func TestForwardGrokResponsesStreamingDefaultsEmptyModelTo45AndSnapshots(t *test
require.Equal(t, xai.DefaultCLIBaseURL+"/responses", upstream.lastReq.URL.String())
require.Equal(t, "Bearer access-token", upstream.lastReq.Header.Get("Authorization"))
require.Equal(t, "responses=experimental", upstream.lastReq.Header.Get("OpenAI-Beta"))
require.Equal(t, "grok-4.5", gjson.GetBytes(upstream.lastBody, "model").String())
require.Equal(t, "grok-4.6", gjson.GetBytes(upstream.lastBody, "model").String())
require.NotEmpty(t, gjson.GetBytes(upstream.lastBody, "prompt_cache_key").String())
require.Equal(t, gjson.GetBytes(upstream.lastBody, "prompt_cache_key").String(), upstream.lastReq.Header.Get(grokConversationIDHeader))
require.Equal(t, "web_search", gjson.GetBytes(upstream.lastBody, "tools.0.type").String())
@@ -1908,7 +1908,7 @@ func TestForwardGrokResponsesAPIKeyUsesXAIResponses(t *testing.T) {
require.Equal(t, "Bearer xai-test-key", upstream.lastReq.Header.Get("Authorization"))
require.Empty(t, upstream.lastReq.Header.Get("X-Grok-Client-Version"))
require.NotEqual(t, grokUpstreamUserAgent, upstream.lastReq.Header.Get("User-Agent"))
require.Equal(t, "grok-4.5", gjson.GetBytes(upstream.lastBody, "model").String())
require.Equal(t, "grok-4.6", gjson.GetBytes(upstream.lastBody, "model").String())
require.Equal(t, "resp_grok_api_key", result.ResponseID)
require.Equal(t, 2, result.Usage.InputTokens)
require.Equal(t, 1, result.Usage.OutputTokens)
@@ -2410,7 +2410,7 @@ func TestForwardGrokResponsesNonStreamingUsesCacheIdentityAndCachedUsage(t *test
require.Equal(t, 7, result.Usage.InputTokens)
require.Equal(t, 2, result.Usage.OutputTokens)
require.Equal(t, 4, result.Usage.CacheReadInputTokens)
require.Equal(t, "grok-4.5", gjson.GetBytes(upstream.lastBody, "model").String())
require.Equal(t, "grok-4.6", gjson.GetBytes(upstream.lastBody, "model").String())
identity := gjson.GetBytes(upstream.lastBody, "prompt_cache_key").String()
require.NotEmpty(t, identity)
require.Equal(t, identity, upstream.lastReq.Header.Get(grokConversationIDHeader))
@@ -1187,7 +1187,7 @@ func TestProxyResponsesWebSocketFromClientForGrokUsesXAIHTTPBridgeAndPreservesMa
require.Equal(t, "Bearer access-token", upstream.lastReq.Header.Get("Authorization"))
require.Equal(t, xai.CLIUserAgent(xai.CLIClientVersion), upstream.lastReq.Header.Get("User-Agent"))
require.Equal(t, grokCLIVersion, upstream.lastReq.Header.Get("X-Grok-Client-Version"))
require.Equal(t, "grok-4.5", gjson.GetBytes(upstream.bodies[0], "model").String())
require.Equal(t, "grok-4.6", gjson.GetBytes(upstream.bodies[0], "model").String())
require.Equal(t, "grok-4.3", gjson.GetBytes(upstream.bodies[1], "model").String())
require.Equal(t, "grok-4.3", gjson.GetBytes(upstream.bodies[2], "model").String())
require.NotEmpty(t, gjson.GetBytes(upstream.lastBody, "prompt_cache_key").String())