mirror of
https://github.com/Wei-Shaw/sub2api.git
synced 2026-10-07 10:23:36 +08:00
fix(anthropic): avoid Fable OAuth system prompt refusal
修复 Fable OAuth 请求因系统提示词形态导致空响应的问题。
This commit is contained in:
@@ -391,6 +391,7 @@ func (s *GatewayService) applyClaudeCodeOAuthMimicryToBody(
|
||||
systemPromptInjectionEnabled, systemPrompt, systemPromptBlocks := s.claudeOAuthSystemPromptInjectionSettings(ctx)
|
||||
systemRewritten := false
|
||||
if systemPromptInjectionEnabled {
|
||||
systemPromptBlocks = claudeOAuthSystemPromptBlocksForModel(model, systemPromptBlocks)
|
||||
body = rewriteSystemForNonClaudeCodeWithPromptBlocks(body, normalizeSystemParam(systemRaw), systemPrompt, systemPromptBlocks)
|
||||
systemRewritten = true
|
||||
}
|
||||
@@ -698,6 +699,24 @@ type claudeOAuthSystemPromptBlocksEnvelope struct {
|
||||
Blocks []claudeOAuthSystemPromptBlockConfig `json:"blocks"`
|
||||
}
|
||||
|
||||
// claudeFableOAuthSystemPromptBlocks keeps the Claude Code identity required by
|
||||
// OAuth credentials without the generic CLI expansion block. Fable 5 rejects
|
||||
// that expansion upstream with stop_reason=refusal and zero output tokens,
|
||||
// while the native billing + identity shape is accepted. Original client
|
||||
// system instructions are still migrated into the message history by
|
||||
// rewriteSystemForNonClaudeCodeWithPromptBlocks.
|
||||
const claudeFableOAuthSystemPromptBlocks = `[
|
||||
{"type":"text","text":"{billing_header}"},
|
||||
{"type":"text","text":"{claude_code_system_prompt}"}
|
||||
]`
|
||||
|
||||
func claudeOAuthSystemPromptBlocksForModel(model, configured string) string {
|
||||
if isAnthropicFableModel(model) {
|
||||
return claudeFableOAuthSystemPromptBlocks
|
||||
}
|
||||
return configured
|
||||
}
|
||||
|
||||
func defaultClaudeOAuthExpansionPrompt(expansionPrompt string) string {
|
||||
expansionPrompt = strings.TrimSpace(expansionPrompt)
|
||||
if expansionPrompt == "" {
|
||||
|
||||
@@ -358,6 +358,25 @@ func TestApplyClaudeCodeOAuthMimicryToBody_HaikuRewritesSystem(t *testing.T) {
|
||||
require.Equal(t, "claude-haiku-4-5-20251001", gjson.GetBytes(out, "model").String())
|
||||
}
|
||||
|
||||
func TestApplyClaudeCodeOAuthMimicryToBody_FableOmitsRefusedExpansion(t *testing.T) {
|
||||
account := &Account{ID: 406, Platform: PlatformAnthropic, Type: AccountTypeOAuth}
|
||||
body := []byte(`{"model":"claude-fable-5","system":"Project instructions","messages":[{"role":"user","content":"hello"}]}`)
|
||||
svc := &GatewayService{cfg: &config.Config{}}
|
||||
|
||||
out := svc.applyClaudeCodeOAuthMimicryToBody(
|
||||
context.Background(), nil, account, body, "Project instructions", "claude-fable-5",
|
||||
)
|
||||
|
||||
system := gjson.GetBytes(out, "system").Array()
|
||||
require.Len(t, system, 2)
|
||||
require.Contains(t, system[0].Get("text").String(), "x-anthropic-billing-header:")
|
||||
require.Equal(t, claudeCodeSystemPrompt, system[1].Get("text").String())
|
||||
require.NotContains(t, string(out), claudeCodeSystemPromptExpansion)
|
||||
require.Contains(t, gjson.GetBytes(out, "messages.0.content.0.text").String(), "Project instructions")
|
||||
require.Equal(t, "Understood. I will follow these instructions.", gjson.GetBytes(out, "messages.1.content.0.text").String())
|
||||
require.Equal(t, "hello", gjson.GetBytes(out, "messages.2.content").String())
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// passthrough 集成测试:buildUpstreamRequest-
|
||||
// AnthropicAPIKeyPassthrough 与 buildCountTokensRequestAnthropicAPIKeyPassthrough
|
||||
|
||||
Reference in New Issue
Block a user