mirror of
https://github.com/router-for-me/CLIProxyAPI.git
synced 2026-06-24 12:57:44 +08:00
feat(executor): add session isolation for grok-composer models
- Introduced `xaiRequiresIsolatedConversation` to enforce session ID generation for `grok-composer` models. - Updated request preparation logic to handle isolated conversations by setting `prompt_cache_key` and `x-grok-conv-id`. - Added unit tests with coverage for session isolation, stateless models, and explicit `prompt_cache_key` scenarios. Closes: #3750
This commit is contained in:
@@ -13,6 +13,7 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
xaiauth "github.com/router-for-me/CLIProxyAPI/v7/internal/auth/xai"
|
||||
"github.com/router-for-me/CLIProxyAPI/v7/internal/config"
|
||||
"github.com/router-for-me/CLIProxyAPI/v7/internal/runtime/executor/helps"
|
||||
@@ -49,6 +50,7 @@ const (
|
||||
xaiVideosExtensionsPath = "/videos/extensions"
|
||||
xaiVideosPath = "/videos"
|
||||
xaiIdempotencyKeyMetaKey = "idempotency_key"
|
||||
xaiComposerModelPrefix = "grok-composer-"
|
||||
)
|
||||
|
||||
// XAIExecutor is a stateless executor for xAI Grok's Responses API.
|
||||
@@ -837,6 +839,9 @@ func (e *XAIExecutor) prepareResponsesRequestTo(ctx context.Context, req cliprox
|
||||
body = sanitizeXAIResponsesBody(body, baseModel)
|
||||
|
||||
sessionID := xaiExecutionSessionID(req, opts)
|
||||
if sessionID == "" && xaiRequiresIsolatedConversation(baseModel) {
|
||||
sessionID = uuid.NewString()
|
||||
}
|
||||
if sessionID != "" {
|
||||
body, _ = sjson.SetBytes(body, "prompt_cache_key", sessionID)
|
||||
}
|
||||
@@ -925,6 +930,10 @@ func xaiExecutionSessionID(req cliproxyexecutor.Request, opts cliproxyexecutor.O
|
||||
return ""
|
||||
}
|
||||
|
||||
func xaiRequiresIsolatedConversation(model string) bool {
|
||||
return strings.HasPrefix(strings.ToLower(strings.TrimSpace(model)), xaiComposerModelPrefix)
|
||||
}
|
||||
|
||||
func xaiImageEndpointPath(opts cliproxyexecutor.Options) string {
|
||||
if opts.SourceFormat.String() != xaiImageHandlerType {
|
||||
return ""
|
||||
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/router-for-me/CLIProxyAPI/v7/internal/config"
|
||||
_ "github.com/router-for-me/CLIProxyAPI/v7/internal/translator"
|
||||
cliproxyauth "github.com/router-for-me/CLIProxyAPI/v7/sdk/cliproxy/auth"
|
||||
@@ -159,6 +160,100 @@ func TestXAIExecutorExecuteShapesResponsesRequest(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestXAIExecutorComposerSessionIsolation(t *testing.T) {
|
||||
exec := NewXAIExecutor(&config.Config{})
|
||||
auth := &cliproxyauth.Auth{
|
||||
Provider: "xai",
|
||||
Metadata: map[string]any{"access_token": "xai-token"},
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
model string
|
||||
payload []byte
|
||||
wantGenerated bool
|
||||
wantSession string
|
||||
}{
|
||||
{
|
||||
name: "composer_generates_fresh_session",
|
||||
model: "grok-composer-2.5-fast",
|
||||
payload: []byte(`{"model":"grok-composer-2.5-fast","input":"hello"}`),
|
||||
wantGenerated: true,
|
||||
},
|
||||
{
|
||||
name: "grok_build_stays_stateless_without_session",
|
||||
model: "grok-build-0.1",
|
||||
payload: []byte(`{"model":"grok-build-0.1","input":"hello"}`),
|
||||
},
|
||||
{
|
||||
name: "explicit_prompt_cache_key_is_preserved",
|
||||
model: "grok-composer-2.5-fast",
|
||||
payload: []byte(`{"model":"grok-composer-2.5-fast","prompt_cache_key":"client-session","input":"hello"}`),
|
||||
wantSession: "client-session",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
prepared, err := exec.prepareResponsesRequest(context.Background(), cliproxyexecutor.Request{
|
||||
Model: tt.model,
|
||||
Payload: tt.payload,
|
||||
}, cliproxyexecutor.Options{
|
||||
SourceFormat: sdktranslator.FormatOpenAIResponse,
|
||||
Stream: true,
|
||||
}, true)
|
||||
if err != nil {
|
||||
t.Fatalf("prepareResponsesRequest() error = %v", err)
|
||||
}
|
||||
|
||||
gotSession := prepared.sessionID
|
||||
gotPromptCacheKey := gjson.GetBytes(prepared.body, "prompt_cache_key").String()
|
||||
httpReq, errRequest := http.NewRequest(http.MethodPost, "https://example.test/responses", bytes.NewReader(prepared.body))
|
||||
if errRequest != nil {
|
||||
t.Fatalf("NewRequest() error = %v", errRequest)
|
||||
}
|
||||
applyXAIHeaders(httpReq, auth, "xai-token", true, gotSession)
|
||||
gotGrokConvID := httpReq.Header.Get("x-grok-conv-id")
|
||||
|
||||
if tt.wantGenerated {
|
||||
if _, errParse := uuid.Parse(gotSession); errParse != nil {
|
||||
t.Fatalf("generated sessionID = %q, want UUID; body=%s", gotSession, string(prepared.body))
|
||||
}
|
||||
if gotPromptCacheKey != gotSession {
|
||||
t.Fatalf("prompt_cache_key = %q, want sessionID %q; body=%s", gotPromptCacheKey, gotSession, string(prepared.body))
|
||||
}
|
||||
if gotGrokConvID != gotSession {
|
||||
t.Fatalf("x-grok-conv-id = %q, want sessionID %q", gotGrokConvID, gotSession)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if tt.wantSession != "" {
|
||||
if gotSession != tt.wantSession {
|
||||
t.Fatalf("sessionID = %q, want %q", gotSession, tt.wantSession)
|
||||
}
|
||||
if gotPromptCacheKey != tt.wantSession {
|
||||
t.Fatalf("prompt_cache_key = %q, want %q; body=%s", gotPromptCacheKey, tt.wantSession, string(prepared.body))
|
||||
}
|
||||
if gotGrokConvID != tt.wantSession {
|
||||
t.Fatalf("x-grok-conv-id = %q, want %q", gotGrokConvID, tt.wantSession)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if gotSession != "" {
|
||||
t.Fatalf("sessionID = %q, want empty", gotSession)
|
||||
}
|
||||
if gotPromptCacheKey != "" {
|
||||
t.Fatalf("prompt_cache_key = %q, want empty; body=%s", gotPromptCacheKey, string(prepared.body))
|
||||
}
|
||||
if gotGrokConvID != "" {
|
||||
t.Fatalf("x-grok-conv-id = %q, want empty", gotGrokConvID)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestXAIExecutorCompactUsesCompactEndpoint(t *testing.T) {
|
||||
var gotPath string
|
||||
var gotAuth string
|
||||
|
||||
Reference in New Issue
Block a user