package clients import ( "fmt" "math" "strconv" "strings" "github.com/easyai/easyai-ai-gateway/apps/api/internal/store" ) const OpenAIReasoningEffortValidationMessage = "reasoning_effort must be one of: none, minimal, low, medium, high, xhigh, max" var ( chatReasoningEffortOrder = []string{"none", "minimal", "low", "medium", "high", "xhigh", "max"} openAIReasoningEfforts = map[string]struct{}{ "none": {}, "minimal": {}, "low": {}, "medium": {}, "high": {}, "xhigh": {}, "max": {}, } volcesChatReasoningEfforts = map[string]struct{}{ "minimal": {}, "low": {}, "medium": {}, "high": {}, } zhipuReasoningEfforts = map[string]struct{}{ "none": {}, "minimal": {}, "low": {}, "medium": {}, "high": {}, "xhigh": {}, } ) func ValidateOpenAIReasoningEffort(value any) error { effort := normalizedReasoningString(value) if effort == "" { return nil } if isOpenAIReasoningEffort(effort) { return nil } return &ClientError{Code: "invalid_parameter", Message: OpenAIReasoningEffortValidationMessage, Retryable: false} } func applyOpenAIChatReasoningParams(body map[string]any, candidate store.RuntimeModelCandidate) { effort := normalizedReasoningString(body["reasoning_effort"]) model := chatReasoningModelName(body, candidate) if isAliyunBailianOpenAI(candidate) && isAliyunQwen38MaxPreview(model) { applyAliyunQwen38Reasoning(body, effort) if temperature, ok := finiteFloatFromAny(body["temperature"]); ok && temperature < 0.6 { body["temperature"] = 0.6 } return } if effort == "" || !isOpenAIReasoningEffort(effort) { return } resolved, state := resolveCandidateReasoningEffort(effort, candidate) if state == reasoningCapabilityUnsupported { delete(body, "reasoning_effort") return } if resolved != "" { effort = resolved } body["reasoning_effort"] = effort switch { case isAliyunBailianOpenAI(candidate): applyAliyunReasoning(body, candidate, effort) case isDeepSeekOpenAI(candidate): applyHighMaxThinkingReasoning(body, effort) case isZhipuOpenAI(candidate): applyZhipuReasoning(body, candidate, effort) case isVolcesOpenAI(candidate): applyVolcesReasoning(body, candidate, effort) } } func applyOpenAIResponsesReasoningParams(body map[string]any, candidate store.RuntimeModelCandidate) { reasoning, _ := body["reasoning"].(map[string]any) if reasoning != nil { reasoning = cloneBody(reasoning) } effort := "" if reasoning != nil { effort = normalizedReasoningString(reasoning["effort"]) } model := chatReasoningModelName(body, candidate) qwen38 := isAliyunBailianOpenAI(candidate) && isAliyunQwen38MaxPreview(model) if qwen38 { if effort == "" { if enabled, ok := body["enable_thinking"].(bool); ok && !enabled { effort = "low" } } else { effort = qwen38MaxPreviewReasoningEffort(effort) } } else if effort != "" && isOpenAIReasoningEffort(effort) { resolved, state := resolveCandidateReasoningEffort(effort, candidate) if state == reasoningCapabilityUnsupported { effort = "" } else if resolved != "" { effort = resolved } } if effort != "" { if reasoning == nil { reasoning = map[string]any{} } reasoning["effort"] = effort } else if reasoning != nil { delete(reasoning, "effort") } if len(reasoning) > 0 { body["reasoning"] = reasoning } else { delete(body, "reasoning") } delete(body, "reasoning_effort") delete(body, "enable_thinking") if qwen38 { if temperature, ok := finiteFloatFromAny(body["temperature"]); ok && temperature < 0.6 { body["temperature"] = 0.6 } } } func applyAliyunQwen38Reasoning(body map[string]any, effort string) { defer delete(body, "thinking_budget_tokens") requestedDisable := false if enabled, ok := body["enable_thinking"].(bool); ok && !enabled { requestedDisable = true } body["enable_thinking"] = true budget, hasBudget := nonNegativeIntFromAny(body["thinking_budget"]) if !hasBudget { budget, hasBudget = nonNegativeIntFromAny(body["thinking_budget_tokens"]) } if hasBudget { if budget > 262144 { budget = 262144 } body["thinking_budget"] = budget delete(body, "reasoning_effort") return } delete(body, "thinking_budget") if effort == "" { if requestedDisable { body["reasoning_effort"] = "low" } return } body["reasoning_effort"] = qwen38MaxPreviewReasoningEffort(effort) } func qwen38MaxPreviewReasoningEffort(effort string) string { switch effort { case "none", "minimal", "low": return "low" case "medium": return "medium" default: return "xhigh" } } type reasoningCapabilityState int const ( reasoningCapabilityUnknown reasoningCapabilityState = iota reasoningCapabilityUnsupported reasoningCapabilitySupported ) func resolveCandidateReasoningEffort(effort string, candidate store.RuntimeModelCandidate) (string, reasoningCapabilityState) { supported := map[string]struct{}{} declaredLevels := false explicitlyUnsupported := false explicitlySupported := false for _, key := range []string{"text_generate", "tools_call", "image_analysis", "video_understanding", "audio_understanding", "omni"} { capability, ok := candidate.Capabilities[key].(map[string]any) if !ok { continue } if value, ok := capability["supportThinking"].(bool); ok && !value { explicitlyUnsupported = true continue } if value, ok := capability["supportThinking"].(bool); ok && value { explicitlySupported = true } rawLevels, ok := capability["thinkingEffortLevels"].([]any) if !ok { if stringsValue, stringsOK := capability["thinkingEffortLevels"].([]string); stringsOK { rawLevels = make([]any, len(stringsValue)) for index, value := range stringsValue { rawLevels[index] = value } ok = true } } if !ok { continue } declaredLevels = true for _, raw := range rawLevels { level := normalizedReasoningString(raw) if isOpenAIReasoningEffort(level) { supported[level] = struct{}{} } } } if len(supported) == 0 { if declaredLevels || (explicitlyUnsupported && !explicitlySupported) { return "", reasoningCapabilityUnsupported } return effort, reasoningCapabilityUnknown } requestedIndex := reasoningEffortIndex(effort) best := "" bestDistance := len(chatReasoningEffortOrder) + 1 for index, candidateEffort := range chatReasoningEffortOrder { if _, ok := supported[candidateEffort]; !ok { continue } distance := index - requestedIndex if distance < 0 { distance = -distance } if distance < bestDistance { best = candidateEffort bestDistance = distance } } return best, reasoningCapabilitySupported } func reasoningEffortIndex(effort string) int { for index, value := range chatReasoningEffortOrder { if value == effort { return index } } return 0 } func applyAliyunReasoning(body map[string]any, candidate store.RuntimeModelCandidate, effort string) { defer delete(body, "thinking_budget_tokens") budget, hasBudget := positiveIntFromAny(body["thinking_budget"]) if !hasBudget { budget, hasBudget = positiveIntFromAny(body["thinking_budget_tokens"]) } delete(body, "thinking_budget") if effort == "none" { body["enable_thinking"] = false delete(body, "reasoning_effort") return } body["enable_thinking"] = true model := chatReasoningModelName(body, candidate) if isAliyunQwen38MaxPreview(model) { if budget, ok := positiveIntFromAny(body["thinking_budget_tokens"]); ok { body["thinking_budget"] = budget delete(body, "reasoning_effort") return } body["reasoning_effort"] = qwen38MaxPreviewReasoningEffort(effort) return } if isAliyunHighMaxReasoningModel(model) { body["reasoning_effort"] = highMaxReasoningEffort(effort) return } delete(body, "reasoning_effort") if isAliyunThinkingBudgetModel(model) { if hasBudget { body["thinking_budget"] = budget } } } func applyHighMaxThinkingReasoning(body map[string]any, effort string) { if effort == "none" { body["thinking"] = map[string]any{"type": "disabled"} delete(body, "reasoning_effort") return } body["thinking"] = map[string]any{"type": "enabled"} body["reasoning_effort"] = highMaxReasoningEffort(effort) } func applyZhipuReasoning(body map[string]any, candidate store.RuntimeModelCandidate, effort string) { if effort == "none" { body["thinking"] = map[string]any{"type": "disabled"} delete(body, "reasoning_effort") return } body["thinking"] = map[string]any{"type": "enabled"} if !isZhipuReasoningEffortModel(chatReasoningModelName(body, candidate)) { delete(body, "reasoning_effort") return } if mapped := zhipuReasoningEffort(effort); mapped != "" { body["reasoning_effort"] = mapped return } delete(body, "reasoning_effort") } func applyVolcesReasoning(body map[string]any, _ store.RuntimeModelCandidate, effort string) { if effort == "none" { body["thinking"] = map[string]any{"type": "disabled"} delete(body, "reasoning_effort") return } body["thinking"] = map[string]any{"type": "enabled"} if mapped := volcesChatReasoningEffort(effort); mapped != "" { body["reasoning_effort"] = mapped return } delete(body, "reasoning_effort") } func normalizedReasoningString(value any) string { if value == nil { return "" } text, ok := value.(string) if !ok { text = fmt.Sprint(value) } return strings.ToLower(strings.TrimSpace(text)) } func isOpenAIReasoningEffort(effort string) bool { _, ok := openAIReasoningEfforts[effort] return ok } func chatReasoningModelName(body map[string]any, candidate store.RuntimeModelCandidate) string { for _, value := range []any{ body["model"], candidate.ProviderModelName, candidate.ModelName, candidate.ModelAlias, } { if text := normalizedReasoningString(value); text != "" { return text } } return "" } func providerCode(candidate store.RuntimeModelCandidate) string { return normalizedReasoningString(candidate.Provider) } func baseURLCode(candidate store.RuntimeModelCandidate) string { return normalizedReasoningString(candidate.BaseURL) } func isAliyunBailianOpenAI(candidate store.RuntimeModelCandidate) bool { provider := providerCode(candidate) baseURL := baseURLCode(candidate) return provider == "aliyun-bailian-openai" || strings.Contains(baseURL, "dashscope.") } func isVolcesOpenAI(candidate store.RuntimeModelCandidate) bool { provider := providerCode(candidate) baseURL := baseURLCode(candidate) return provider == "volces-openai" || strings.Contains(baseURL, "volces.com") || strings.Contains(baseURL, "byteplus.com") } func isDeepSeekOpenAI(candidate store.RuntimeModelCandidate) bool { provider := providerCode(candidate) baseURL := baseURLCode(candidate) return provider == "deepseek-openai" || strings.Contains(baseURL, "api.deepseek.com") } func isZhipuOpenAI(candidate store.RuntimeModelCandidate) bool { provider := providerCode(candidate) baseURL := baseURLCode(candidate) return provider == "zhipu-openai" || strings.Contains(baseURL, "bigmodel.cn") || strings.Contains(baseURL, "api.z.ai") } func isAliyunHighMaxReasoningModel(model string) bool { return strings.Contains(model, "deepseek-v4") || strings.HasPrefix(model, "glm-") } func isAliyunQwen38MaxPreview(model string) bool { return strings.Contains(model, "qwen3.8-max-preview") } func isAliyunThinkingBudgetModel(model string) bool { return strings.Contains(model, "qwen") || strings.Contains(model, "qwq") || strings.Contains(model, "qvq") || strings.Contains(model, "kimi") } func isVolcesReasoningEffortModel(model string) bool { return strings.HasPrefix(model, "doubao-seed-2-") } func isZhipuReasoningEffortModel(model string) bool { return model == "" || strings.HasPrefix(model, "glm-5.2") || strings.HasPrefix(model, "glm-5-2") } func highMaxReasoningEffort(effort string) string { if effort == "xhigh" || effort == "max" { return "max" } return "high" } func zhipuReasoningEffort(effort string) string { if effort == "max" { return "xhigh" } if _, ok := zhipuReasoningEfforts[effort]; ok { return effort } return "" } func volcesChatReasoningEffort(effort string) string { switch effort { case "none": return "minimal" case "xhigh", "max": return "high" default: if _, ok := volcesChatReasoningEfforts[effort]; ok { return effort } return "" } } func positiveIntFromAny(value any) (int, bool) { if value == nil { return 0, false } var number float64 switch typed := value.(type) { case int: number = float64(typed) case int64: number = float64(typed) case float64: number = typed case float32: number = float64(typed) case jsonNumber: parsed, err := typed.Float64() if err != nil { return 0, false } number = parsed case string: parsed := normalizedReasoningString(typed) if parsed == "" { return 0, false } value, err := strconv.ParseFloat(parsed, 64) if err != nil { return 0, false } number = value default: return 0, false } if math.IsNaN(number) || math.IsInf(number, 0) || number <= 0 { return 0, false } return int(math.Floor(number)), true } func nonNegativeIntFromAny(value any) (int, bool) { number, ok := finiteFloatFromAny(value) if !ok || number < 0 { return 0, false } return int(math.Floor(number)), true } func finiteFloatFromAny(value any) (float64, bool) { if value == nil { return 0, false } var number float64 switch typed := value.(type) { case int: number = float64(typed) case int64: number = float64(typed) case float64: number = typed case float32: number = float64(typed) case jsonNumber: parsed, err := typed.Float64() if err != nil { return 0, false } number = parsed case string: parsed, err := strconv.ParseFloat(strings.TrimSpace(typed), 64) if err != nil { return 0, false } number = parsed default: return 0, false } if math.IsNaN(number) || math.IsInf(number, 0) { return 0, false } return number, true } type jsonNumber interface { Float64() (float64, error) }