From adcf6328b4d6bab1ef4be1ee065b2d78f7da865c Mon Sep 17 00:00:00 2001 From: erio Date: Tue, 31 Mar 2026 20:11:39 +0800 Subject: [PATCH] =?UTF-8?q?fix(openai):=20=E9=9D=9E=20OAuth=20=E8=B4=A6?= =?UTF-8?q?=E5=8F=B7=20Chat=20Completions=20=E4=B9=9F=E6=8F=90=E5=8F=96=20?= =?UTF-8?q?system=20=E6=B6=88=E6=81=AF=E5=B9=B6=E6=B3=A8=E5=85=A5=20instru?= =?UTF-8?q?ctions?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit GPT-5/Codex 等模型要求请求中必须包含 instructions 字段。 之前 extractSystemMessagesFromInput + applyInstructions 仅在 OAuth 路径执行,API Key 账号走 Chat Completions 时 system 消息 保留在 input 中但缺少 instructions,导致上游返回 400。 同时在 ChatCompletionsRequest 和 ResponsesRequest 中增加 Instructions 字段,支持客户端直接传入 instructions 透传到上游。 --- .../apicompat/chatcompletions_to_responses.go | 5 +-- backend/internal/pkg/apicompat/types.go | 2 ++ .../openai_gateway_chat_completions.go | 33 ++++++++++++++----- 3 files changed, 29 insertions(+), 11 deletions(-) diff --git a/backend/internal/pkg/apicompat/chatcompletions_to_responses.go b/backend/internal/pkg/apicompat/chatcompletions_to_responses.go index 6cdd012a49..5ea8eb2877 100644 --- a/backend/internal/pkg/apicompat/chatcompletions_to_responses.go +++ b/backend/internal/pkg/apicompat/chatcompletions_to_responses.go @@ -27,8 +27,9 @@ func ChatCompletionsToResponses(req *ChatCompletionsRequest) (*ResponsesRequest, } out := &ResponsesRequest{ - Model: req.Model, - Input: inputJSON, + Model: req.Model, + Instructions: req.Instructions, + Input: inputJSON, Temperature: req.Temperature, TopP: req.TopP, Stream: true, // upstream always streams diff --git a/backend/internal/pkg/apicompat/types.go b/backend/internal/pkg/apicompat/types.go index b724a5ed96..d9546cb008 100644 --- a/backend/internal/pkg/apicompat/types.go +++ b/backend/internal/pkg/apicompat/types.go @@ -152,6 +152,7 @@ type AnthropicDelta struct { // ResponsesRequest is the request body for POST /v1/responses. type ResponsesRequest struct { Model string `json:"model"` + Instructions string `json:"instructions,omitempty"` Input json.RawMessage `json:"input"` // string or []ResponsesInputItem MaxOutputTokens *int `json:"max_output_tokens,omitempty"` Temperature *float64 `json:"temperature,omitempty"` @@ -337,6 +338,7 @@ type ResponsesStreamEvent struct { type ChatCompletionsRequest struct { Model string `json:"model"` Messages []ChatMessage `json:"messages"` + Instructions string `json:"instructions,omitempty"` // OpenAI Responses API compat MaxTokens *int `json:"max_tokens,omitempty"` MaxCompletionTokens *int `json:"max_completion_tokens,omitempty"` Temperature *float64 `json:"temperature,omitempty"` diff --git a/backend/internal/service/openai_gateway_chat_completions.go b/backend/internal/service/openai_gateway_chat_completions.go index a442da33bb..026e69d38f 100644 --- a/backend/internal/service/openai_gateway_chat_completions.go +++ b/backend/internal/service/openai_gateway_chat_completions.go @@ -82,20 +82,35 @@ func (s *OpenAIGatewayService) ForwardAsChatCompletions( return nil, fmt.Errorf("marshal responses request: %w", err) } - if account.Type == AccountTypeOAuth { + { var reqBody map[string]any if err := json.Unmarshal(responsesBody, &reqBody); err != nil { return nil, fmt.Errorf("unmarshal for codex transform: %w", err) } - codexResult := applyCodexOAuthTransform(reqBody, false, false) - if codexResult.PromptCacheKey != "" { - promptCacheKey = codexResult.PromptCacheKey - } else if promptCacheKey != "" { - reqBody["prompt_cache_key"] = promptCacheKey + modified := false + if account.Type == AccountTypeOAuth { + codexResult := applyCodexOAuthTransform(reqBody, false, false) + modified = codexResult.Modified + if codexResult.PromptCacheKey != "" { + promptCacheKey = codexResult.PromptCacheKey + } else if promptCacheKey != "" { + reqBody["prompt_cache_key"] = promptCacheKey + } + } else { + // 非 OAuth 账号也需要提取 system 消息并注入 instructions, + // 否则上游 GPT-5/Codex 等模型会报 "Instructions are required"。 + if extractSystemMessagesFromInput(reqBody) { + modified = true + } + if applyInstructions(reqBody, false) { + modified = true + } } - responsesBody, err = json.Marshal(reqBody) - if err != nil { - return nil, fmt.Errorf("remarshal after codex transform: %w", err) + if modified { + responsesBody, err = json.Marshal(reqBody) + if err != nil { + return nil, fmt.Errorf("remarshal after codex transform: %w", err) + } } }