diff --git a/coding-plan.md b/coding-plan.md new file mode 100644 index 000000000000..2f31c8db721f --- /dev/null +++ b/coding-plan.md @@ -0,0 +1,16 @@ +火山引擎 订阅计划: +1、个人版、企业版 Coding Plan +BaseURL: +https://ark.cn-beijing.volces.com/api/coding/v3 (兼容OpenAI 协议) +https://ark.cn-beijing.volces.com/api/coding (兼容 Anthropic 接口协议) +阿里云百炼 订阅计划: +1、企业版 token plan: +BaseURL: +https://token-plan.cn-beijing.maas.aliyuncs.com/compatible-mode/v1 (兼容OpenAI 协议) +https://token-plan.cn-beijing.maas.aliyuncs.com/apps/anthropic (兼容 Anthropic 接口协议) +2、个人版 coding plan: +https://coding.dashscope.aliyuncs.com/v1 (兼容OpenAI 协议) +https://coding.dashscope.aliyuncs.com/apps/anthropic (兼容 Anthropic 接口协议) +智普GLM 订阅计划: +1、个人版 coding plan: +https://open.bigmodel.cn/api/coding/paas/v4 \ No newline at end of file diff --git a/constant/channel.go b/constant/channel.go index 48502bedc52c..ab7f7ee2b316 100644 --- a/constant/channel.go +++ b/constant/channel.go @@ -206,4 +206,12 @@ var ChannelSpecialBases = map[string]ChannelSpecialBase{ ClaudeBaseURL: "https://ark.cn-beijing.volces.com/api/coding", OpenAIBaseURL: "https://ark.cn-beijing.volces.com/api/coding/v3", }, + "ali-token-plan": { + ClaudeBaseURL: "https://token-plan.cn-beijing.maas.aliyuncs.com/apps/anthropic", + OpenAIBaseURL: "https://token-plan.cn-beijing.maas.aliyuncs.com/compatible-mode/v1", + }, + "ali-coding-plan": { + ClaudeBaseURL: "https://coding.dashscope.aliyuncs.com/apps/anthropic", + OpenAIBaseURL: "https://coding.dashscope.aliyuncs.com/v1", + }, } diff --git a/controller/channel_upstream_update.go b/controller/channel_upstream_update.go index 77a1e3c817a8..20a91a162b96 100644 --- a/controller/channel_upstream_update.go +++ b/controller/channel_upstream_update.go @@ -292,7 +292,11 @@ func fetchChannelUpstreamModelIDs(channel *model.Channel) ([]string, error) { var url string switch channel.Type { case constant.ChannelTypeAli: - url = fmt.Sprintf("%s/compatible-mode/v1/models", baseURL) + if plan, ok := constant.ChannelSpecialBases[baseURL]; ok && plan.OpenAIBaseURL != "" { + url = fmt.Sprintf("%s/models", plan.OpenAIBaseURL) + } else { + url = fmt.Sprintf("%s/compatible-mode/v1/models", baseURL) + } case constant.ChannelTypeZhipu_v4: if plan, ok := constant.ChannelSpecialBases[baseURL]; ok && plan.OpenAIBaseURL != "" { url = fmt.Sprintf("%s/models", plan.OpenAIBaseURL) diff --git a/relay/channel/ali/adaptor.go b/relay/channel/ali/adaptor.go index cb3070ff367e..b5fce1402f58 100644 --- a/relay/channel/ali/adaptor.go +++ b/relay/channel/ali/adaptor.go @@ -7,6 +7,7 @@ import ( "net/http" "strings" + channelconstant "github.com/QuantumNous/new-api/constant" "github.com/QuantumNous/new-api/common" "github.com/QuantumNous/new-api/dto" "github.com/QuantumNous/new-api/relay/channel" @@ -71,6 +72,9 @@ func (a *Adaptor) ConvertGeminiRequest(*gin.Context, *relaycommon.RelayInfo, *dt } func (a *Adaptor) ConvertClaudeRequest(c *gin.Context, info *relaycommon.RelayInfo, req *dto.ClaudeRequest) (any, error) { + if _, ok := channelconstant.ChannelSpecialBases[info.ChannelBaseUrl]; ok { + return req, nil + } if supportsAliAnthropicMessages(info.UpstreamModelName) { return req, nil } @@ -89,49 +93,57 @@ func (a *Adaptor) Init(info *relaycommon.RelayInfo) { } func (a *Adaptor) GetRequestURL(info *relaycommon.RelayInfo) (string, error) { - var fullRequestURL string + baseUrl := info.ChannelBaseUrl + specialPlan, hasSpecialPlan := channelconstant.ChannelSpecialBases[baseUrl] + switch info.RelayFormat { case types.RelayFormatClaude: + if hasSpecialPlan && specialPlan.ClaudeBaseURL != "" { + return fmt.Sprintf("%s/v1/messages", specialPlan.ClaudeBaseURL), nil + } if supportsAliAnthropicMessages(info.UpstreamModelName) { - fullRequestURL = fmt.Sprintf("%s/apps/anthropic/v1/messages", info.ChannelBaseUrl) - } else { - fullRequestURL = fmt.Sprintf("%s/compatible-mode/v1/chat/completions", info.ChannelBaseUrl) + return fmt.Sprintf("%s/apps/anthropic/v1/messages", baseUrl), nil } + return fmt.Sprintf("%s/compatible-mode/v1/chat/completions", baseUrl), nil default: + if hasSpecialPlan && specialPlan.OpenAIBaseURL != "" { + return fmt.Sprintf("%s/chat/completions", specialPlan.OpenAIBaseURL), nil + } switch info.RelayMode { case constant.RelayModeEmbeddings: - fullRequestURL = fmt.Sprintf("%s/compatible-mode/v1/embeddings", info.ChannelBaseUrl) + return fmt.Sprintf("%s/compatible-mode/v1/embeddings", baseUrl), nil case constant.RelayModeRerank: - fullRequestURL = fmt.Sprintf("%s/api/v1/services/rerank/text-rerank/text-rerank", info.ChannelBaseUrl) + return fmt.Sprintf("%s/api/v1/services/rerank/text-rerank/text-rerank", baseUrl), nil case constant.RelayModeResponses: - fullRequestURL = fmt.Sprintf("%s/api/v2/apps/protocols/compatible-mode/v1/responses", info.ChannelBaseUrl) + return fmt.Sprintf("%s/api/v2/apps/protocols/compatible-mode/v1/responses", baseUrl), nil case constant.RelayModeImagesGenerations: if isSyncImageModel(info.OriginModelName) { - fullRequestURL = fmt.Sprintf("%s/api/v1/services/aigc/multimodal-generation/generation", info.ChannelBaseUrl) + return fmt.Sprintf("%s/api/v1/services/aigc/multimodal-generation/generation", baseUrl), nil } else { - fullRequestURL = fmt.Sprintf("%s/api/v1/services/aigc/text2image/image-synthesis", info.ChannelBaseUrl) + return fmt.Sprintf("%s/api/v1/services/aigc/text2image/image-synthesis", baseUrl), nil } case constant.RelayModeImagesEdits: if isOldWanModel(info.OriginModelName) { - fullRequestURL = fmt.Sprintf("%s/api/v1/services/aigc/image2image/image-synthesis", info.ChannelBaseUrl) + return fmt.Sprintf("%s/api/v1/services/aigc/image2image/image-synthesis", baseUrl), nil } else if isWanModel(info.OriginModelName) { - fullRequestURL = fmt.Sprintf("%s/api/v1/services/aigc/image-generation/generation", info.ChannelBaseUrl) + return fmt.Sprintf("%s/api/v1/services/aigc/image-generation/generation", baseUrl), nil } else { - fullRequestURL = fmt.Sprintf("%s/api/v1/services/aigc/multimodal-generation/generation", info.ChannelBaseUrl) + return fmt.Sprintf("%s/api/v1/services/aigc/multimodal-generation/generation", baseUrl), nil } case constant.RelayModeCompletions: - fullRequestURL = fmt.Sprintf("%s/compatible-mode/v1/completions", info.ChannelBaseUrl) + return fmt.Sprintf("%s/compatible-mode/v1/completions", baseUrl), nil default: - fullRequestURL = fmt.Sprintf("%s/compatible-mode/v1/chat/completions", info.ChannelBaseUrl) + return fmt.Sprintf("%s/compatible-mode/v1/chat/completions", baseUrl), nil } } - - return fullRequestURL, nil } func (a *Adaptor) SetupRequestHeader(c *gin.Context, req *http.Header, info *relaycommon.RelayInfo) error { channel.SetupApiRequestHeader(info, c, req) req.Set("Authorization", "Bearer "+info.ApiKey) + if _, ok := channelconstant.ChannelSpecialBases[info.ChannelBaseUrl]; ok { + return nil + } if info.IsStream { req.Set("X-DashScope-SSE", "enable") } @@ -158,18 +170,9 @@ func (a *Adaptor) ConvertOpenAIRequest(c *gin.Context, info *relaycommon.RelayIn if request == nil { return nil, errors.New("request is nil") } - // docs: https://bailian.console.aliyun.com/?tab=api#/api/?type=model&url=2712216 - // fix: InternalError.Algo.InvalidParameter: The value of the enable_thinking parameter is restricted to True. - //if strings.Contains(request.Model, "thinking") { - // request.EnableThinking = true - // request.Stream = true - // info.IsStream = true - //} - //// fix: ali parameter.enable_thinking must be set to false for non-streaming calls - //if !info.IsStream { - // request.EnableThinking = false - //} - + if _, ok := channelconstant.ChannelSpecialBases[info.ChannelBaseUrl]; ok { + return request, nil + } switch info.RelayMode { default: aliReq := requestOpenAI2Ali(*request) @@ -241,7 +244,7 @@ func (a *Adaptor) DoRequest(c *gin.Context, info *relaycommon.RelayInfo, request func (a *Adaptor) DoResponse(c *gin.Context, resp *http.Response, info *relaycommon.RelayInfo) (usage any, err *types.NewAPIError) { switch info.RelayFormat { case types.RelayFormatClaude: - if supportsAliAnthropicMessages(info.UpstreamModelName) { + if _, ok := channelconstant.ChannelSpecialBases[info.ChannelBaseUrl]; ok || supportsAliAnthropicMessages(info.UpstreamModelName) { adaptor := claude.Adaptor{} return adaptor.DoResponse(c, resp, info) } diff --git a/web/default/src/features/channels/components/drawers/channel-mutate-drawer.tsx b/web/default/src/features/channels/components/drawers/channel-mutate-drawer.tsx index 1d5ada517166..6d6980d5d01c 100644 --- a/web/default/src/features/channels/components/drawers/channel-mutate-drawer.tsx +++ b/web/default/src/features/channels/components/drawers/channel-mutate-drawer.tsx @@ -393,11 +393,24 @@ export function ChannelMutateDrawer({ }, }) + const { + unlocked: aliApiEditUnlocked, + handleClick: handleAliApiConfigSecretClick, + reset: resetAliApiUnlock, + } = useHiddenClickUnlock({ + requiredClicks: 10, + disabled: currentType !== 17, + onUnlock: () => { + toast.info(t('Ali custom API address editing unlocked')) + }, + }) + useEffect(() => { if (!open) { resetDoubaoApiUnlock() + resetAliApiUnlock() } - }, [open, resetDoubaoApiUnlock]) + }, [open, resetDoubaoApiUnlock, resetAliApiUnlock]) // Helper computed values const isBatchMode = @@ -616,6 +629,14 @@ export function ChannelMutateDrawer({ } } + // Type 17 (Ali) - set default base_url + if (currentType === 17) { + const currentBaseUrlValue = form.getValues('base_url') + if (!currentBaseUrlValue || currentBaseUrlValue === '') { + form.setValue('base_url', 'https://dashscope.aliyuncs.com') + } + } + // Type 18 (Xunfei) - set default other (version) if (currentType === 18) { const currentOther = form.getValues('other') @@ -1728,6 +1749,76 @@ export function ChannelMutateDrawer({ /> )} + {/* Ali/DashScope (type 17) */} + {currentType === 17 && !aliApiEditUnlocked && ( + ( + + + {t('API Base URL *')} + + + + {t('Select the API endpoint')} + + + + )} + /> + )} + + {/* Ali/DashScope (type 17) - Custom API URL (unlocked) */} + {currentType === 17 && aliApiEditUnlocked && ( + ( + + {t('API Base URL *')} + + + + + {t('Enter custom API endpoint URL')} + + + + )} + /> + )} + {/* Coze (type 49) */} {currentType === 49 && (