fix(gateway): 修复 OpenAI→Anthropic 转换路径 system prompt 被静默丢弃的 bug
injectClaudeCodePrompt 和 systemIncludesClaudeCodePrompt 的 type switch 无法匹配 json.RawMessage 类型(Go typed nil 陷阱),导致 ForwardAsResponses 和 ForwardAsChatCompletions 路径中用户 system prompt 被替换为仅 Claude Code banner。新增 normalizeSystemParam 将 json.RawMessage 转为标准 Go 类型。 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -124,6 +124,27 @@ func TestSystemIncludesClaudeCodePrompt(t *testing.T) {
|
|||||||
},
|
},
|
||||||
want: false,
|
want: false,
|
||||||
},
|
},
|
||||||
|
// json.RawMessage cases (conversion path: ForwardAsResponses / ForwardAsChatCompletions)
|
||||||
|
{
|
||||||
|
name: "json.RawMessage string with Claude Code prompt",
|
||||||
|
system: json.RawMessage(`"` + claudeCodeSystemPrompt + `"`),
|
||||||
|
want: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "json.RawMessage string without Claude Code prompt",
|
||||||
|
system: json.RawMessage(`"You are a helpful assistant"`),
|
||||||
|
want: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "json.RawMessage nil (empty)",
|
||||||
|
system: json.RawMessage(nil),
|
||||||
|
want: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "json.RawMessage empty string",
|
||||||
|
system: json.RawMessage(`""`),
|
||||||
|
want: false,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
@@ -202,6 +223,29 @@ func TestInjectClaudeCodePrompt(t *testing.T) {
|
|||||||
wantSystemLen: 1,
|
wantSystemLen: 1,
|
||||||
wantFirstText: claudeCodeSystemPrompt,
|
wantFirstText: claudeCodeSystemPrompt,
|
||||||
},
|
},
|
||||||
|
// json.RawMessage cases (conversion path: ForwardAsResponses / ForwardAsChatCompletions)
|
||||||
|
{
|
||||||
|
name: "json.RawMessage string system",
|
||||||
|
body: `{"model":"claude-3","system":"Custom prompt"}`,
|
||||||
|
system: json.RawMessage(`"Custom prompt"`),
|
||||||
|
wantSystemLen: 2,
|
||||||
|
wantFirstText: claudeCodeSystemPrompt,
|
||||||
|
wantSecondText: claudePrefix + "\n\nCustom prompt",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "json.RawMessage nil system",
|
||||||
|
body: `{"model":"claude-3"}`,
|
||||||
|
system: json.RawMessage(nil),
|
||||||
|
wantSystemLen: 1,
|
||||||
|
wantFirstText: claudeCodeSystemPrompt,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "json.RawMessage Claude Code prompt (should not duplicate)",
|
||||||
|
body: `{"model":"claude-3","system":"` + claudeCodeSystemPrompt + `"}`,
|
||||||
|
system: json.RawMessage(`"` + claudeCodeSystemPrompt + `"`),
|
||||||
|
wantSystemLen: 1,
|
||||||
|
wantFirstText: claudeCodeSystemPrompt,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
|
|||||||
@@ -3749,9 +3749,28 @@ func isClaudeCodeRequest(ctx context.Context, c *gin.Context, parsed *ParsedRequ
|
|||||||
return isClaudeCodeClient(c.GetHeader("User-Agent"), parsed.MetadataUserID)
|
return isClaudeCodeClient(c.GetHeader("User-Agent"), parsed.MetadataUserID)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// normalizeSystemParam 将 json.RawMessage 类型的 system 参数转为标准 Go 类型(string / []any / nil),
|
||||||
|
// 避免 type switch 中 json.RawMessage(底层 []byte)无法匹配 case string / case []any / case nil 的问题。
|
||||||
|
// 这是 Go 的 typed nil 陷阱:(json.RawMessage, nil) ≠ (nil, nil)。
|
||||||
|
func normalizeSystemParam(system any) any {
|
||||||
|
raw, ok := system.(json.RawMessage)
|
||||||
|
if !ok {
|
||||||
|
return system
|
||||||
|
}
|
||||||
|
if len(raw) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
var parsed any
|
||||||
|
if err := json.Unmarshal(raw, &parsed); err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return parsed
|
||||||
|
}
|
||||||
|
|
||||||
// systemIncludesClaudeCodePrompt 检查 system 中是否已包含 Claude Code 提示词
|
// systemIncludesClaudeCodePrompt 检查 system 中是否已包含 Claude Code 提示词
|
||||||
// 使用前缀匹配支持多种变体(标准版、Agent SDK 版等)
|
// 使用前缀匹配支持多种变体(标准版、Agent SDK 版等)
|
||||||
func systemIncludesClaudeCodePrompt(system any) bool {
|
func systemIncludesClaudeCodePrompt(system any) bool {
|
||||||
|
system = normalizeSystemParam(system)
|
||||||
switch v := system.(type) {
|
switch v := system.(type) {
|
||||||
case string:
|
case string:
|
||||||
return hasClaudeCodePrefix(v)
|
return hasClaudeCodePrefix(v)
|
||||||
@@ -3780,6 +3799,7 @@ func hasClaudeCodePrefix(text string) bool {
|
|||||||
// injectClaudeCodePrompt 在 system 开头注入 Claude Code 提示词
|
// injectClaudeCodePrompt 在 system 开头注入 Claude Code 提示词
|
||||||
// 处理 null、字符串、数组三种格式
|
// 处理 null、字符串、数组三种格式
|
||||||
func injectClaudeCodePrompt(body []byte, system any) []byte {
|
func injectClaudeCodePrompt(body []byte, system any) []byte {
|
||||||
|
system = normalizeSystemParam(system)
|
||||||
claudeCodeBlock, err := marshalAnthropicSystemTextBlock(claudeCodeSystemPrompt, true)
|
claudeCodeBlock, err := marshalAnthropicSystemTextBlock(claudeCodeSystemPrompt, true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.LegacyPrintf("service.gateway", "Warning: failed to build Claude Code prompt block: %v", err)
|
logger.LegacyPrintf("service.gateway", "Warning: failed to build Claude Code prompt block: %v", err)
|
||||||
|
|||||||
Reference in New Issue
Block a user