fix(haiku): 修正采样边界、默认模型配置与 lint

This commit is contained in:
shaw
2026-10-13 14:38:16 +08:00
parent 26f70c6a87
commit c4bdc48475
7 changed files with 27 additions and 10 deletions
@@ -1966,13 +1966,15 @@ func TestHaiku55ResponsesThinkingAndSampling(t *testing.T) {
temperature := 0.7
_, err := ResponsesToAnthropicRequest(&ResponsesRequest{Model: "claude-haiku-5-5", Input: json.RawMessage(`"hello"`), Temperature: &temperature})
require.ErrorContains(t, err, "temperature")
topP := 0.5
_, err = ResponsesToAnthropicRequest(&ResponsesRequest{Model: "claude-haiku-5-5", Input: json.RawMessage(`"hello"`), TopP: &topP})
require.ErrorContains(t, err, "top_p")
for _, topP := range []float64{0.5, 0.995, 1} {
_, err = ResponsesToAnthropicRequest(&ResponsesRequest{Model: "claude-haiku-5-5", Input: json.RawMessage(`"hello"`), TopP: &topP})
require.ErrorContains(t, err, "top_p", "top_p=%v", topP)
}
_, err = ResponsesToAnthropicRequest(&ResponsesRequest{Model: "claude-haiku-5-5", Input: json.RawMessage(`"hello"`), Reasoning: &ResponsesReasoning{Effort: "minimal"}})
require.ErrorContains(t, err, "reasoning effort")
temperature, topP = 1, 0.99
temperature = 1
topP := 0.99
_, err = ResponsesToAnthropicRequest(&ResponsesRequest{Model: "claude-haiku-5-5", Input: json.RawMessage(`"hello"`), Temperature: &temperature})
require.NoError(t, err)
_, err = ResponsesToAnthropicRequest(&ResponsesRequest{Model: "claude-haiku-5-5", Input: json.RawMessage(`"hello"`), TopP: &topP})
@@ -89,7 +89,7 @@ func ResponsesToAnthropicRequest(req *ResponsesRequest) (*AnthropicRequest, erro
if req.Temperature != nil && *req.Temperature != 1 {
return nil, fmt.Errorf("claude-haiku-5-5 does not support non-default temperature")
}
if req.TopP != nil && (*req.TopP < 0.99 || *req.TopP > 1) {
if req.TopP != nil && *req.TopP != 0.99 {
return nil, fmt.Errorf("claude-haiku-5-5 does not support non-default top_p")
}
if req.Temperature != nil && req.TopP != nil {
@@ -267,7 +267,7 @@ func normalizeClaudeOAuthRequestBody(body []byte, modelID string, opts claudeOAu
// 客户端只传 top_p 时不得再补 temperature,否则网关自己制造 400(issue #7919)。
samplingLocked := claude.IsSonnet55(modelID) || claude.IsHaiku55(modelID)
if !gjson.GetBytes(out, "temperature").Exists() && !claude.IsOpus55(modelID) &&
!(samplingLocked && gjson.GetBytes(out, "top_p").Exists()) {
(!samplingLocked || !gjson.GetBytes(out, "top_p").Exists()) {
if next, ok := setJSONValueBytes(out, "temperature", 1); ok {
out = next
modified = true
+3 -1
View File
@@ -639,7 +639,9 @@ func validateClaude55Request(body []byte, model string) error {
if temperature := gjson.GetBytes(body, "temperature"); temperature.Exists() && (temperature.Type != gjson.Number || temperature.Float() != 1) {
return fmt.Errorf("%s does not support non-default temperature", modelName)
}
if topP := gjson.GetBytes(body, "top_p"); topP.Exists() && (topP.Type != gjson.Number || topP.Float() < 0.99 || topP.Float() > 1) {
// Haiku 5.5 只接受显式 top_p=0.99;保留 Sonnet 5.5 既有的区间策略。
if topP := gjson.GetBytes(body, "top_p"); topP.Exists() && (topP.Type != gjson.Number ||
(isHaiku55 && topP.Float() != 0.99) || (isSonnet55 && (topP.Float() < 0.99 || topP.Float() > 1))) {
return fmt.Errorf("%s does not support non-default top_p", modelName)
}
if gjson.GetBytes(body, "top_k").Exists() {
@@ -22,6 +22,7 @@ func TestValidateClaude55Request_Haiku55(t *testing.T) {
{name: "adaptive thinking passes", extra: `,"thinking":{"type":"adaptive"},"output_config":{"effort":"medium"}`},
{name: "omitted thinking passes", extra: ``},
{name: "enabled thinking rejected", extra: `,"thinking":{"type":"enabled","budget_tokens":8000}`, wantErr: "requires adaptive thinking"},
{name: "拒绝 between_tools 并返回 Haiku 错误说明", extra: `,"thinking":{"type":"between_tools"}`, wantErr: "claude-haiku-5-5 requires adaptive thinking; thinking.type=between_tools is unsupported"},
{name: "disabled thinking allowed at medium effort", extra: `,"thinking":{"type":"disabled"},"output_config":{"effort":"medium"}`},
{name: "disabled thinking allowed when effort omitted", extra: `,"thinking":{"type":"disabled"}`},
{name: "disabled thinking rejected at xhigh", extra: `,"thinking":{"type":"disabled"},"output_config":{"effort":"xhigh"}`, wantErr: "supports only low, medium or high effort"},
@@ -29,9 +30,9 @@ func TestValidateClaude55Request_Haiku55(t *testing.T) {
{name: "temperature 1 passes", extra: `,"temperature":1`},
{name: "non-default temperature rejected", extra: `,"temperature":0.5`, wantErr: "non-default temperature"},
{name: "top_p 0.99 passes", extra: `,"top_p":0.99`},
// 与 sonnet-5-5 同口径:区间 [0.99, 1.0] 放行,越界才拦;
// 官方称 top_p=1 会 400,如上游收紧以它为准,网关保持 fail-open。
{name: "top_p 1 passes (sonnet bar)", extra: `,"top_p":1`},
{name: "拒绝 top_p=0.995", extra: `,"top_p":0.995`, wantErr: "non-default top_p"},
{name: "拒绝 top_p=1", extra: `,"top_p":1`, wantErr: "non-default top_p"},
{name: "拒绝字符串 top_p", extra: `,"top_p":"0.99"`, wantErr: "non-default top_p"},
{name: "top_p 0.5 rejected", extra: `,"top_p":0.5`, wantErr: "non-default top_p"},
{name: "top_k rejected", extra: `,"top_k":5`, wantErr: "does not support top_k"},
{name: "temperature and top_p together rejected", extra: `,"temperature":1,"top_p":0.99`, wantErr: "does not allow temperature and top_p together"},
@@ -71,6 +71,16 @@ describe('useModelWhitelist', () => {
]))
})
it('Claude 默认白名单保存后允许 Haiku 5.5,预设使用同一模型 ID', () => {
const models: string[] = getModelsByPlatform('anthropic')
const mapping: Record<string, string> | null = buildModelMappingObject('whitelist', models, [])
expect(mapping?.['claude-haiku-5-5']).toBe('claude-haiku-5-5')
expect(getPresetMappingsByPlatform('anthropic')).toEqual(expect.arrayContaining([
expect.objectContaining({ label: 'Haiku 5.5', from: 'claude-haiku-5-5', to: 'claude-haiku-5-5' })
]))
})
it('xAI 模型列表包含 Grok 4.5 官方模型和别名', () => {
const models = getModelsByPlatform('grok')
@@ -37,6 +37,7 @@ export const claudeModels = [
'claude-opus-5-5',
'claude-opus-5',
'claude-sonnet-5-5',
'claude-haiku-5-5',
'claude-sonnet-5',
'claude-sonnet-4-6',
'claude-fable-5-1',
@@ -297,6 +298,7 @@ const anthropicPresetMappings = [
{ label: 'Opus 5', from: 'claude-opus-5', to: 'claude-opus-5', color: 'bg-purple-100 text-purple-700 hover:bg-purple-200 dark:bg-purple-900/30 dark:text-purple-400' },
{ label: 'Haiku 3.5', from: 'claude-3-5-haiku-20241022', to: 'claude-3-5-haiku-20241022', color: 'bg-green-100 text-green-700 hover:bg-green-200 dark:bg-green-900/30 dark:text-green-400' },
{ label: 'Haiku 4.5', from: 'claude-haiku-4-5-20251001', to: 'claude-haiku-4-5-20251001', color: 'bg-emerald-100 text-emerald-700 hover:bg-emerald-200 dark:bg-emerald-900/30 dark:text-emerald-400' },
{ label: 'Haiku 5.5', from: 'claude-haiku-5-5', to: 'claude-haiku-5-5', color: 'bg-emerald-100 text-emerald-700 hover:bg-emerald-200 dark:bg-emerald-900/30 dark:text-emerald-400' },
{ label: 'Opus->Sonnet', from: 'claude-opus-4-6', to: 'claude-sonnet-4-5-20250929', color: 'bg-amber-100 text-amber-700 hover:bg-amber-200 dark:bg-amber-900/30 dark:text-amber-400' }
]