feat: support upstream model fetch for advanced custom channels (#5971)
* feat: support upstream model fetch for advanced custom channels * fix: add advanced custom routes as separate groups * fix: select advanced custom route entry before adding --------- Co-authored-by: CaIon <i@caion.me>
This commit is contained in:
+135
-33
@@ -16,6 +16,7 @@ import (
|
||||
"github.com/QuantumNous/new-api/model"
|
||||
relaychannel "github.com/QuantumNous/new-api/relay/channel"
|
||||
"github.com/QuantumNous/new-api/relay/channel/ollama"
|
||||
relaycommon "github.com/QuantumNous/new-api/relay/common"
|
||||
"github.com/QuantumNous/new-api/service"
|
||||
"github.com/QuantumNous/new-api/service/authz"
|
||||
|
||||
@@ -201,22 +202,29 @@ func buildFetchModelsHeaders(channel *model.Channel, key string) (http.Header, e
|
||||
headers = GetAuthHeader(key)
|
||||
}
|
||||
|
||||
headerOverride := channel.GetHeaderOverride()
|
||||
for k, v := range headerOverride {
|
||||
if relaychannel.IsHeaderPassthroughRuleKey(k) {
|
||||
continue
|
||||
}
|
||||
str, ok := v.(string)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("invalid header override for key %s", k)
|
||||
}
|
||||
if strings.Contains(str, "{api_key}") {
|
||||
str = strings.ReplaceAll(str, "{api_key}", key)
|
||||
}
|
||||
headers.Set(k, str)
|
||||
if err := applyFetchModelsHeaderOverrides(channel, key, headers); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return headers, nil
|
||||
}
|
||||
|
||||
func applyFetchModelsHeaderOverrides(channel *model.Channel, key string, headers http.Header) error {
|
||||
info := &relaycommon.RelayInfo{
|
||||
IsChannelTest: true,
|
||||
ChannelMeta: &relaycommon.ChannelMeta{
|
||||
ApiKey: key,
|
||||
HeadersOverride: channel.GetHeaderOverride(),
|
||||
},
|
||||
}
|
||||
overrides, err := relaychannel.ResolveHeaderOverride(info, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for name, value := range overrides {
|
||||
headers.Set(name, value)
|
||||
}
|
||||
|
||||
return headers, nil
|
||||
return nil
|
||||
}
|
||||
|
||||
func FetchUpstreamModels(c *gin.Context) {
|
||||
@@ -464,6 +472,10 @@ func validateTwoFactorAuth(twoFA *model.TwoFA, code string) bool {
|
||||
|
||||
// validateChannel 通用的渠道校验函数
|
||||
func validateChannel(channel *model.Channel, isAdd bool) error {
|
||||
if channel == nil {
|
||||
return fmt.Errorf("channel cannot be empty")
|
||||
}
|
||||
|
||||
// 校验 channel settings
|
||||
if err := channel.ValidateSettings(); err != nil {
|
||||
return fmt.Errorf("渠道额外设置[channel setting] 格式错误:%s", err.Error())
|
||||
@@ -471,7 +483,7 @@ func validateChannel(channel *model.Channel, isAdd bool) error {
|
||||
|
||||
// 如果是添加操作,检查 channel 和 key 是否为空
|
||||
if isAdd {
|
||||
if channel == nil || channel.Key == "" {
|
||||
if channel.Key == "" {
|
||||
return fmt.Errorf("channel cannot be empty")
|
||||
}
|
||||
|
||||
@@ -1155,13 +1167,87 @@ func equalStringPtr(a, b *string) bool {
|
||||
return *a == *b
|
||||
}
|
||||
|
||||
func FetchModels(c *gin.Context) {
|
||||
var req struct {
|
||||
BaseURL string `json:"base_url"`
|
||||
Type int `json:"type"`
|
||||
Key string `json:"key"`
|
||||
type fetchModelsRequest struct {
|
||||
ChannelID int `json:"channel_id"`
|
||||
BaseURL *string `json:"base_url"`
|
||||
Type int `json:"type"`
|
||||
Key string `json:"key"`
|
||||
AdvancedCustom *string `json:"advanced_custom"`
|
||||
HeaderOverride *string `json:"header_override"`
|
||||
Proxy *string `json:"proxy"`
|
||||
}
|
||||
|
||||
func buildAdvancedCustomModelPreviewChannel(req fetchModelsRequest) (*model.Channel, error) {
|
||||
var channel *model.Channel
|
||||
if req.ChannelID > 0 {
|
||||
savedChannel, err := model.GetChannelById(req.ChannelID, true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if savedChannel.Type != constant.ChannelTypeAdvancedCustom {
|
||||
return nil, fmt.Errorf("channel %d is not an advanced custom channel", req.ChannelID)
|
||||
}
|
||||
channel = savedChannel
|
||||
} else {
|
||||
key := strings.TrimSpace(req.Key)
|
||||
if key != "" {
|
||||
key = strings.Split(key, "\n")[0]
|
||||
}
|
||||
channel = &model.Channel{
|
||||
Type: req.Type,
|
||||
Key: key,
|
||||
}
|
||||
}
|
||||
|
||||
if channel.Type != constant.ChannelTypeAdvancedCustom {
|
||||
return nil, fmt.Errorf("channel type must be advanced custom")
|
||||
}
|
||||
if req.BaseURL != nil {
|
||||
baseURL := strings.TrimSpace(*req.BaseURL)
|
||||
channel.BaseURL = &baseURL
|
||||
}
|
||||
|
||||
settings := channel.GetOtherSettings()
|
||||
if req.AdvancedCustom != nil {
|
||||
rawConfig := strings.TrimSpace(*req.AdvancedCustom)
|
||||
if rawConfig == "" {
|
||||
return nil, fmt.Errorf("advanced_custom is required")
|
||||
}
|
||||
var config dto.AdvancedCustomConfig
|
||||
if err := common.UnmarshalJsonStr(rawConfig, &config); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
settings.AdvancedCustom = &config
|
||||
} else if req.ChannelID <= 0 {
|
||||
return nil, fmt.Errorf("advanced_custom is required")
|
||||
}
|
||||
channel.SetOtherSettings(settings)
|
||||
|
||||
if req.HeaderOverride != nil {
|
||||
rawHeaderOverride := strings.TrimSpace(*req.HeaderOverride)
|
||||
if rawHeaderOverride != "" {
|
||||
var headerOverride map[string]any
|
||||
if err := common.UnmarshalJsonStr(rawHeaderOverride, &headerOverride); err != nil {
|
||||
return nil, fmt.Errorf("header_override must be a JSON object: %w", err)
|
||||
}
|
||||
}
|
||||
channel.HeaderOverride = &rawHeaderOverride
|
||||
}
|
||||
if req.Proxy != nil {
|
||||
channelSettings := channel.GetSetting()
|
||||
channelSettings.Proxy = strings.TrimSpace(*req.Proxy)
|
||||
channel.SetSetting(channelSettings)
|
||||
}
|
||||
|
||||
if err := validateChannel(channel, false); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return channel, nil
|
||||
}
|
||||
|
||||
func FetchModels(c *gin.Context) {
|
||||
var req fetchModelsRequest
|
||||
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{
|
||||
"success": false,
|
||||
@@ -1170,21 +1256,37 @@ func FetchModels(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
baseURL := req.BaseURL
|
||||
if baseURL == "" {
|
||||
baseURL = constant.ChannelBaseURLs[req.Type]
|
||||
var channel *model.Channel
|
||||
if req.Type == constant.ChannelTypeAdvancedCustom || req.ChannelID > 0 {
|
||||
var err error
|
||||
channel, err = buildAdvancedCustomModelPreviewChannel(req)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"success": false,
|
||||
"message": err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
} else {
|
||||
baseURL := ""
|
||||
if req.BaseURL != nil {
|
||||
baseURL = strings.TrimSpace(*req.BaseURL)
|
||||
}
|
||||
if baseURL == "" {
|
||||
baseURL = constant.ChannelBaseURLs[req.Type]
|
||||
}
|
||||
|
||||
key := strings.TrimSpace(req.Key)
|
||||
if req.Type != constant.ChannelTypeCodex {
|
||||
key = strings.Split(key, "\n")[0]
|
||||
}
|
||||
channel = &model.Channel{
|
||||
Type: req.Type,
|
||||
Key: key,
|
||||
BaseURL: &baseURL,
|
||||
}
|
||||
}
|
||||
|
||||
key := strings.TrimSpace(req.Key)
|
||||
if req.Type != constant.ChannelTypeCodex {
|
||||
key = strings.Split(key, "\n")[0]
|
||||
}
|
||||
|
||||
channel := &model.Channel{
|
||||
Type: req.Type,
|
||||
Key: key,
|
||||
BaseURL: &baseURL,
|
||||
}
|
||||
models, err := fetchChannelUpstreamModelIDs(channel)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
|
||||
Reference in New Issue
Block a user