diff --git a/plugins/wasm-go/extensions/ai-proxy/README.md b/plugins/wasm-go/extensions/ai-proxy/README.md index a54a404a1..05852cd6a 100644 --- a/plugins/wasm-go/extensions/ai-proxy/README.md +++ b/plugins/wasm-go/extensions/ai-proxy/README.md @@ -19,14 +19,14 @@ description: AI 代理插件配置参考 `provider`的配置字段说明如下: -| 名称 | 数据类型 | 填写要求 | 默认值 | 描述 | -|----------------|-----------------|------|-----|----------------------------------------------------------------------------------| -| `type` | string | 必填 | - | AI 服务提供商名称。目前支持以下取值:openai, azure, moonshot, qwen, zhipuai, baidu | -| `apiTokens` | array of string | 必填 | - | 用于在访问 AI 服务时进行认证的令牌。如果配置了多个 token,插件会在请求时随机进行选择。部分服务提供商只支持配置一个 token。 | -| `timeout` | number | 非必填 | - | 访问 AI 服务的超时时间。单位为毫秒。默认值为 120000,即 2 分钟 | -| `modelMapping` | map of string | 非必填 | - | AI 模型映射表,用于将请求中的模型名称映射为服务提供商支持模型名称。
可以使用 "*" 为键来配置通用兜底映射关系 | -| `protocol` | string | 非必填 | - | 插件对外提供的 API 接口契约。目前支持以下取值:openai(默认值,使用 OpenAI 的接口契约)、original(使用目标服务提供商的原始接口契约) | -| `context` | object | 非必填 | - | 配置 AI 对话上下文信息 | +| 名称 | 数据类型 | 填写要求 | 默认值 | 描述 | +| -------------- | --------------- | -------- | ------ | ------------------------------------------------------------ | +| `type` | string | 必填 | - | AI 服务提供商名称。目前支持以下取值:openai, azure, moonshot, qwen, zhipuai, baidu, minimax | +| `apiTokens` | array of string | 必填 | - | 用于在访问 AI 服务时进行认证的令牌。如果配置了多个 token,插件会在请求时随机进行选择。部分服务提供商只支持配置一个 token。 | +| `timeout` | number | 非必填 | - | 访问 AI 服务的超时时间。单位为毫秒。默认值为 120000,即 2 分钟 | +| `modelMapping` | map of string | 非必填 | - | AI 模型映射表,用于将请求中的模型名称映射为服务提供商支持模型名称。
可以使用 "*" 为键来配置通用兜底映射关系 | +| `protocol` | string | 非必填 | - | 插件对外提供的 API 接口契约。目前支持以下取值:openai(默认值,使用 OpenAI 的接口契约)、original(使用目标服务提供商的原始接口契约) | +| `context` | object | 非必填 | - | 配置 AI 对话上下文信息 | `context`的配置字段说明如下: @@ -93,6 +93,14 @@ Groq 所对应的 `type` 为 `groq`。它并无特有的配置字段。 文心一言所对应的 `type` 为 `baidu`。它并无特有的配置字段。 +#### MiniMax + +MiniMax所对应的 `type` 为 `minimax`。它特有的配置字段如下: + +| 名称 | 数据类型 | 填写要求 | 默认值 | 描述 | +| ---------------- | -------- | ------------------------------------------------------------ | ------ | ------------------------------------------------------------ | +| `minimaxGroupId` | string | 当使用`abab6.5-chat`, `abab6.5s-chat`, `abab5.5s-chat`, `abab5.5-chat`四种模型时必填 | - | 当使用`abab6.5-chat`, `abab6.5s-chat`, `abab5.5s-chat`, `abab5.5-chat`四种模型时会使用ChatCompletion Pro,需要设置groupID | + #### Anthropic Claude Anthropic Claude 所对应的 `type` 为 `claude`。它特有的配置字段如下: @@ -680,6 +688,69 @@ provider: } ``` +### 使用 OpenAI 协议代理MiniMax服务 + +**配置信息** + +```yaml +provider: + type: minimax + apiTokens: + - "YOUR_MINIMAX_API_TOKEN" + modelMapping: + "gpt-3": "abab6.5g-chat" + "gpt-4": "abab6.5-chat" + "*": "abab6.5g-chat" + minimaxGroupId: "YOUR_MINIMAX_GROUP_ID" +``` + +**请求示例** + +```json +{ + "model": "gpt-4-turbo", + "messages": [ + { + "role": "user", + "content": "你好,你是谁?" + } + ], + "stream": false +} +``` + +**响应示例** + +```json +{ + "id": "02b2251f8c6c09d68c1743f07c72afd7", + "choices": [ + { + "finish_reason": "stop", + "index": 0, + "message": { + "content": "你好!我是MM智能助理,一款由MiniMax自研的大型语言模型。我可以帮助你解答问题,提供信息,进行对话等。有什么可以帮助你的吗?", + "role": "assistant" + } + } + ], + "created": 1717760544, + "model": "abab6.5s-chat", + "object": "chat.completion", + "usage": { + "total_tokens": 106 + }, + "input_sensitive": false, + "output_sensitive": false, + "input_sensitive_type": 0, + "output_sensitive_type": 0, + "base_resp": { + "status_code": 0, + "status_msg": "" + } +} +``` + ## 完整配置示例 以下以使用 OpenAI 协议代理 Groq 服务为例,展示完整的插件配置示例。 diff --git a/plugins/wasm-go/extensions/ai-proxy/provider/baidu.go b/plugins/wasm-go/extensions/ai-proxy/provider/baidu.go index c2a91b5e8..706d3f2ad 100644 --- a/plugins/wasm-go/extensions/ai-proxy/provider/baidu.go +++ b/plugins/wasm-go/extensions/ai-proxy/provider/baidu.go @@ -334,7 +334,5 @@ func (b *baiduProvider) streamResponseBaidu2OpenAI(ctx wrapper.HttpContext, resp } func (b *baiduProvider) appendResponse(responseBuilder *strings.Builder, responseBody string) { - responseBuilder.WriteString(streamDataItemKey) - responseBuilder.WriteString(responseBody) - responseBuilder.WriteString("\n\n") + responseBuilder.WriteString(fmt.Sprintf("%s %s\n\n", streamDataItemKey, responseBody)) } diff --git a/plugins/wasm-go/extensions/ai-proxy/provider/minimax.go b/plugins/wasm-go/extensions/ai-proxy/provider/minimax.go new file mode 100644 index 000000000..73918912d --- /dev/null +++ b/plugins/wasm-go/extensions/ai-proxy/provider/minimax.go @@ -0,0 +1,472 @@ +package provider + +import ( + "encoding/json" + "errors" + "fmt" + "github.com/alibaba/higress/plugins/wasm-go/extensions/ai-proxy/util" + "github.com/alibaba/higress/plugins/wasm-go/pkg/wrapper" + "github.com/higress-group/proxy-wasm-go-sdk/proxywasm" + "github.com/higress-group/proxy-wasm-go-sdk/proxywasm/types" + "strings" +) + +// minimaxProvider is the provider for minimax service. + +const ( + minimaxDomain = "api.minimax.chat" + // minimaxChatCompletionV2Path 接口请求响应格式与OpenAI相同 + // 接口文档: https://platform.minimaxi.com/document/guides/chat-model/V2?id=65e0736ab2845de20908e2dd + minimaxChatCompletionV2Path = "/v1/text/chatcompletion_v2" + // minimaxChatCompletionProPath 接口请求响应格式与OpenAI不同 + // 接口文档: https://platform.minimaxi.com/document/guides/chat-model/pro/api?id=6569c85948bc7b684b30377e + minimaxChatCompletionProPath = "/v1/text/chatcompletion_pro" + + senderTypeUser string = "USER" // 用户发送的内容 + senderTypeBot string = "BOT" // 模型生成的内容 + + // 默认机器人设置 + defaultBotName string = "MM智能助理" + defaultBotSettingContent string = "MM智能助理是一款由MiniMax自研的,没有调用其他产品的接口的大型语言模型。MiniMax是一家中国科技公司,一直致力于进行大模型相关的研究。" + defaultSenderName string = "小明" +) + +// chatCompletionProModels 这些模型对应接口为ChatCompletion Pro +var chatCompletionProModels = map[string]struct{}{ + "abab6.5-chat": {}, + "abab6.5s-chat": {}, + "abab5.5s-chat": {}, + "abab5.5-chat": {}, +} + +type minimaxProviderInitializer struct { +} + +func (m *minimaxProviderInitializer) ValidateConfig(config ProviderConfig) error { + // 如果存在模型对应接口为ChatCompletion Pro必须配置minimaxGroupId + if len(config.modelMapping) > 0 && config.minimaxGroupId == "" { + for _, minimaxModel := range config.modelMapping { + if _, exists := chatCompletionProModels[minimaxModel]; exists { + return errors.New(fmt.Sprintf("missing minimaxGroupId in provider config when %s model is provided", minimaxModel)) + } + } + } + return nil +} + +func (m *minimaxProviderInitializer) CreateProvider(config ProviderConfig) (Provider, error) { + return &minimaxProvider{ + config: config, + contextCache: createContextCache(&config), + }, nil +} + +type minimaxProvider struct { + config ProviderConfig + contextCache *contextCache +} + +func (m *minimaxProvider) GetProviderType() string { + return providerTypeMinimax +} + +func (m *minimaxProvider) OnRequestHeaders(ctx wrapper.HttpContext, apiName ApiName, log wrapper.Log) (types.Action, error) { + if apiName != ApiNameChatCompletion { + return types.ActionContinue, errUnsupportedApiName + } + _ = util.OverwriteRequestHost(minimaxDomain) + _ = proxywasm.ReplaceHttpRequestHeader("Authorization", "Bearer "+m.config.GetRandomToken()) + _ = proxywasm.RemoveHttpRequestHeader("Content-Length") + + // Delay the header processing to allow changing streaming mode in OnRequestBody + return types.HeaderStopIteration, nil +} + +func (m *minimaxProvider) OnRequestBody(ctx wrapper.HttpContext, apiName ApiName, body []byte, log wrapper.Log) (types.Action, error) { + if apiName != ApiNameChatCompletion { + return types.ActionContinue, errUnsupportedApiName + } + // 解析并映射模型,设置上下文 + model, err := m.parseModel(body) + if err != nil { + return types.ActionContinue, err + } + ctx.SetContext(ctxKeyOriginalRequestModel, model) + mappedModel := getMappedModel(model, m.config.modelMapping, log) + if mappedModel == "" { + return types.ActionContinue, errors.New("model becomes empty after applying the configured mapping") + } + ctx.SetContext(ctxKeyFinalRequestModel, mappedModel) + _, ok := chatCompletionProModels[mappedModel] + if ok { + // 使用ChatCompletion Pro接口 + return m.handleRequestBodyByChatCompletionPro(body, log) + } else { + // 使用ChatCompletion v2接口 + return m.handleRequestBodyByChatCompletionV2(body, log) + } +} + +// handleRequestBodyByChatCompletionPro 使用ChatCompletion Pro接口处理请求体 +func (m *minimaxProvider) handleRequestBodyByChatCompletionPro(body []byte, log wrapper.Log) (types.Action, error) { + // 使用minimax接口协议 + if m.config.protocol == protocolOriginal { + request := &minimaxChatCompletionV2Request{} + if err := json.Unmarshal(body, request); err != nil { + return types.ActionContinue, fmt.Errorf("unable to unmarshal request: %v", err) + } + if request.Model == "" { + return types.ActionContinue, errors.New("request model is empty") + } + // 根据模型重写requestPath + if m.config.minimaxGroupId == "" { + return types.ActionContinue, errors.New(fmt.Sprintf("missing minimaxGroupId in provider config when use %s model ", request.Model)) + } + _ = util.OverwriteRequestPath(fmt.Sprintf("%s?GroupId=%s", minimaxChatCompletionProPath, m.config.minimaxGroupId)) + + if m.config.context == nil { + return types.ActionContinue, nil + } + + err := m.contextCache.GetContent(func(content string, err error) { + defer func() { + _ = proxywasm.ResumeHttpRequest() + }() + + if err != nil { + log.Errorf("failed to load context file: %v", err) + _ = util.SendResponse(500, util.MimeTypeTextPlain, fmt.Sprintf("failed to load context file: %v", err)) + } + m.setBotSettings(request, content) + if err := replaceJsonRequestBody(request, log); err != nil { + _ = util.SendResponse(500, util.MimeTypeTextPlain, fmt.Sprintf("failed to replace request body: %v", err)) + } + }, log) + if err == nil { + return types.ActionPause, nil + } + return types.ActionContinue, err + } + + request := &chatCompletionRequest{} + if err := decodeChatCompletionRequest(body, request); err != nil { + return types.ActionContinue, err + } + + // 映射模型重写requestPath + request.Model = getMappedModel(request.Model, m.config.modelMapping, log) + _ = util.OverwriteRequestPath(fmt.Sprintf("%s?GroupId=%s", minimaxChatCompletionProPath, m.config.minimaxGroupId)) + + if m.config.context == nil { + minimaxRequest := m.buildMinimaxChatCompletionV2Request(request, "") + return types.ActionContinue, replaceJsonRequestBody(minimaxRequest, log) + } + + err := m.contextCache.GetContent(func(content string, err error) { + defer func() { + _ = proxywasm.ResumeHttpRequest() + }() + if err != nil { + log.Errorf("failed to load context file: %v", err) + _ = util.SendResponse(500, util.MimeTypeTextPlain, fmt.Sprintf("failed to load context file: %v", err)) + } + minimaxRequest := m.buildMinimaxChatCompletionV2Request(request, content) + if err := replaceJsonRequestBody(minimaxRequest, log); err != nil { + _ = util.SendResponse(500, util.MimeTypeTextPlain, fmt.Sprintf("failed to replace Request body: %v", err)) + } + }, log) + if err == nil { + return types.ActionPause, nil + } + return types.ActionContinue, err +} + +// handleRequestBodyByChatCompletionV2 使用ChatCompletion v2接口处理请求体 +func (m *minimaxProvider) handleRequestBodyByChatCompletionV2(body []byte, log wrapper.Log) (types.Action, error) { + request := &chatCompletionRequest{} + if err := decodeChatCompletionRequest(body, request); err != nil { + return types.ActionContinue, err + } + + // 映射模型重写requestPath + request.Model = getMappedModel(request.Model, m.config.modelMapping, log) + _ = util.OverwriteRequestPath(minimaxChatCompletionV2Path) + + if m.contextCache == nil { + return types.ActionContinue, replaceJsonRequestBody(request, log) + } + + err := m.contextCache.GetContent(func(content string, err error) { + defer func() { + _ = proxywasm.ResumeHttpRequest() + }() + if err != nil { + log.Errorf("failed to load context file: %v", err) + _ = util.SendResponse(500, util.MimeTypeTextPlain, fmt.Sprintf("failed to load context file: %v", err)) + } + insertContextMessage(request, content) + if err := replaceJsonRequestBody(request, log); err != nil { + _ = util.SendResponse(500, util.MimeTypeTextPlain, fmt.Sprintf("failed to replace request body: %v", err)) + } + }, log) + if err == nil { + return types.ActionPause, nil + } + return types.ActionContinue, err +} + +func (m *minimaxProvider) OnResponseHeaders(ctx wrapper.HttpContext, apiName ApiName, log wrapper.Log) (types.Action, error) { + // 使用minimax接口协议,跳过OnStreamingResponseBody()和OnResponseBody() + if m.config.protocol == protocolOriginal { + ctx.DontReadResponseBody() + return types.ActionContinue, nil + } + // 模型对应接口为ChatCompletion v2,跳过OnStreamingResponseBody()和OnResponseBody() + model := ctx.GetContext(ctxKeyFinalRequestModel) + if model != nil { + _, ok := chatCompletionProModels[model.(string)] + if !ok { + ctx.DontReadResponseBody() + return types.ActionContinue, nil + } + } + _ = proxywasm.RemoveHttpResponseHeader("Content-Length") + return types.ActionContinue, nil +} + +// OnStreamingResponseBody 只处理使用OpenAI协议 且 模型对应接口为ChatCompletion Pro的流式响应 +func (m *minimaxProvider) OnStreamingResponseBody(ctx wrapper.HttpContext, name ApiName, chunk []byte, isLastChunk bool, log wrapper.Log) ([]byte, error) { + if isLastChunk || len(chunk) == 0 { + return nil, nil + } + // sample event response: + // data: {"created":1689747645,"model":"abab6.5s-chat","reply":"","choices":[{"messages":[{"sender_type":"BOT","sender_name":"MM智能助理","text":"am from China."}]}],"output_sensitive":false} + + // sample end event response: + // data: {"created":1689747645,"model":"abab6.5s-chat","reply":"I am from China.","choices":[{"finish_reason":"stop","messages":[{"sender_type":"BOT","sender_name":"MM智能助理","text":"I am from China."}]}],"usage":{"total_tokens":187},"input_sensitive":false,"output_sensitive":false,"id":"0106b3bc9fd844a9f3de1aa06004e2ab","base_resp":{"status_code":0,"status_msg":""}} + responseBuilder := &strings.Builder{} + lines := strings.Split(string(chunk), "\n") + for _, data := range lines { + if len(data) < 6 { + // ignore blank line or wrong format + continue + } + data = data[6:] + var minimaxResp minimaxChatCompletionV2Resp + if err := json.Unmarshal([]byte(data), &minimaxResp); err != nil { + log.Errorf("unable to unmarshal minimax response: %v", err) + continue + } + response := m.responseV2ToOpenAI(&minimaxResp) + responseBody, err := json.Marshal(response) + if err != nil { + log.Errorf("unable to marshal response: %v", err) + return nil, err + } + m.appendResponse(responseBuilder, string(responseBody)) + } + modifiedResponseChunk := responseBuilder.String() + log.Debugf("=== modified response chunk: %s", modifiedResponseChunk) + return []byte(modifiedResponseChunk), nil +} + +// OnResponseBody 只处理使用OpenAI协议 且 模型对应接口为ChatCompletion Pro的流式响应 +func (m *minimaxProvider) OnResponseBody(ctx wrapper.HttpContext, apiName ApiName, body []byte, log wrapper.Log) (types.Action, error) { + minimaxResp := &minimaxChatCompletionV2Resp{} + if err := json.Unmarshal(body, minimaxResp); err != nil { + return types.ActionContinue, fmt.Errorf("unable to unmarshal minimax response: %v", err) + } + if minimaxResp.BaseResp.StatusCode != 0 { + return types.ActionContinue, fmt.Errorf("minimax response error, error_code: %d, error_message: %s", minimaxResp.BaseResp.StatusCode, minimaxResp.BaseResp.StatusMsg) + } + response := m.responseV2ToOpenAI(minimaxResp) + return types.ActionContinue, replaceJsonResponseBody(response, log) +} + +// minimaxChatCompletionV2Request 表示ChatCompletion V2请求的结构体 +type minimaxChatCompletionV2Request struct { + Model string `json:"model"` + Stream bool `json:"stream,omitempty"` + TokensToGenerate int64 `json:"tokens_to_generate,omitempty"` + Temperature float64 `json:"temperature,omitempty"` + TopP float64 `json:"top_p,omitempty"` + MaskSensitiveInfo bool `json:"mask_sensitive_info"` // 是否开启隐私信息打码,默认true + Messages []minimaxMessage `json:"messages"` + BotSettings []minimaxBotSetting `json:"bot_setting"` + ReplyConstraints minimaxReplyConstraints `json:"reply_constraints"` +} + +// minimaxMessage 表示对话中的消息 +type minimaxMessage struct { + SenderType string `json:"sender_type"` + SenderName string `json:"sender_name"` + Text string `json:"text"` +} + +// minimaxBotSetting 表示机器人的设置 +type minimaxBotSetting struct { + BotName string `json:"bot_name"` + Content string `json:"content"` +} + +// minimaxReplyConstraints 表示模型回复要求 +type minimaxReplyConstraints struct { + SenderType string `json:"sender_type"` + SenderName string `json:"sender_name"` +} + +// minimaxChatCompletionV2Resp Minimax Chat Completion V2响应结构体 +type minimaxChatCompletionV2Resp struct { + Created int64 `json:"created"` + Model string `json:"model"` + Reply string `json:"reply"` + InputSensitive bool `json:"input_sensitive,omitempty"` + InputSensitiveType int64 `json:"input_sensitive_type,omitempty"` + OutputSensitive bool `json:"output_sensitive,omitempty"` + OutputSensitiveType int64 `json:"output_sensitive_type,omitempty"` + Choices []minimaxChoice `json:"choices,omitempty"` + Usage minimaxUsage `json:"usage,omitempty"` + Id string `json:"id"` + BaseResp minimaxBaseResp `json:"base_resp"` +} + +// minimaxBaseResp 包含错误状态码和详情 +type minimaxBaseResp struct { + StatusCode int64 `json:"status_code"` + StatusMsg string `json:"status_msg"` +} + +// minimaxChoice 结果选项 +type minimaxChoice struct { + Messages []minimaxMessage `json:"messages"` + Index int64 `json:"index"` + FinishReason string `json:"finish_reason"` +} + +// minimaxUsage 令牌使用情况 +type minimaxUsage struct { + TotalTokens int64 `json:"total_tokens"` +} + +func (m *minimaxProvider) parseModel(body []byte) (string, error) { + var tempMap map[string]interface{} + if err := json.Unmarshal(body, &tempMap); err != nil { + return "", err + } + model, ok := tempMap["model"].(string) + if !ok { + return "", errors.New("missing model in chat completion request") + } + return model, nil +} + +func (m *minimaxProvider) setBotSettings(request *minimaxChatCompletionV2Request, botSettingContent string) { + if len(request.BotSettings) == 0 { + request.BotSettings = []minimaxBotSetting{ + { + BotName: defaultBotName, + Content: func() string { + if botSettingContent != "" { + return botSettingContent + } + return defaultBotSettingContent + }(), + }, + } + } else if botSettingContent != "" { + newSetting := minimaxBotSetting{ + BotName: request.BotSettings[0].BotName, + Content: botSettingContent, + } + request.BotSettings = append([]minimaxBotSetting{newSetting}, request.BotSettings...) + } +} + +func (m *minimaxProvider) buildMinimaxChatCompletionV2Request(request *chatCompletionRequest, botSettingContent string) *minimaxChatCompletionV2Request { + var messages []minimaxMessage + var botSetting []minimaxBotSetting + var botName string + + determineName := func(name string, defaultName string) string { + if name != "" { + return name + } + return defaultName + } + + for _, message := range request.Messages { + switch message.Role { + case roleSystem: + botName = determineName(message.Name, defaultBotName) + botSetting = append(botSetting, minimaxBotSetting{ + BotName: botName, + Content: message.Content, + }) + case roleAssistant: + messages = append(messages, minimaxMessage{ + SenderType: senderTypeBot, + SenderName: determineName(message.Name, defaultBotName), + Text: message.Content, + }) + case roleUser: + messages = append(messages, minimaxMessage{ + SenderType: senderTypeUser, + SenderName: determineName(message.Name, defaultSenderName), + Text: message.Content, + }) + } + } + + replyConstraints := minimaxReplyConstraints{ + SenderType: senderTypeBot, + SenderName: determineName(botName, defaultBotName), + } + result := &minimaxChatCompletionV2Request{ + Model: request.Model, + Stream: request.Stream, + TokensToGenerate: int64(request.MaxTokens), + Temperature: request.Temperature, + TopP: request.TopP, + MaskSensitiveInfo: true, + Messages: messages, + BotSettings: botSetting, + ReplyConstraints: replyConstraints, + } + + m.setBotSettings(result, botSettingContent) + return result +} + +func (m *minimaxProvider) responseV2ToOpenAI(response *minimaxChatCompletionV2Resp) *chatCompletionResponse { + var choices []chatCompletionChoice + messageIndex := 0 + for _, choice := range response.Choices { + for _, message := range choice.Messages { + message := &chatMessage{ + Name: message.SenderName, + Role: roleAssistant, + Content: message.Text, + } + choices = append(choices, chatCompletionChoice{ + FinishReason: choice.FinishReason, + Index: messageIndex, + Message: message, + }) + messageIndex++ + } + } + return &chatCompletionResponse{ + Id: response.Id, + Object: objectChatCompletion, + Created: response.Created, + Model: response.Model, + Choices: choices, + Usage: chatCompletionUsage{ + TotalTokens: int(response.Usage.TotalTokens), + }, + } +} + +func (m *minimaxProvider) appendResponse(responseBuilder *strings.Builder, responseBody string) { + responseBuilder.WriteString(fmt.Sprintf("%s %s\n\n", streamDataItemKey, responseBody)) +} diff --git a/plugins/wasm-go/extensions/ai-proxy/provider/provider.go b/plugins/wasm-go/extensions/ai-proxy/provider/provider.go index d5ef4a6e8..1664b1b69 100644 --- a/plugins/wasm-go/extensions/ai-proxy/provider/provider.go +++ b/plugins/wasm-go/extensions/ai-proxy/provider/provider.go @@ -27,6 +27,7 @@ const ( providerTypeOllama = "ollama" providerTypeBaidu = "baidu" providerTypeHunyuan = "hunyuan" + providerTypeMinimax = "minimax" protocolOpenAI = "openai" protocolOriginal = "original" @@ -73,6 +74,7 @@ var ( providerTypeOllama: &ollamaProviderInitializer{}, providerTypeBaidu: &baiduProviderInitializer{}, providerTypeHunyuan: &hunyuanProviderInitializer{}, + providerTypeMinimax: &minimaxProviderInitializer{}, } ) @@ -102,7 +104,7 @@ type ResponseBodyHandler interface { type ProviderConfig struct { // @Title zh-CN AI服务提供商 - // @Description zh-CN AI服务提供商类型,目前支持的取值为:"moonshot"、"qwen"、"openai"、"azure"、"baichuan"、"yi"、"zhipuai"、"ollama"、"baidu" + // @Description zh-CN AI服务提供商类型,目前支持的取值为:"moonshot"、"qwen"、"openai"、"azure"、"baichuan"、"yi"、"zhipuai"、"ollama"、"baidu"、minimax" typ string `required:"true" yaml:"type" json:"type"` // @Title zh-CN API Tokens // @Description zh-CN 在请求AI服务时用于认证的API Token列表。不同的AI服务提供商可能有不同的名称。部分供应商只支持配置一个API Token(如Azure OpenAI)。 @@ -134,6 +136,9 @@ type ProviderConfig struct { // @Title zh-CN hunyuan api id for authorization // @Description zh-CN 仅适用于Hun Yuan AI服务鉴权 hunyuanAuthId string `required:"false" yaml:"hunyuanAuthId" json:"hunyuanAuthId"` + // @Title zh-CN minimax group id + // @Description zh-CN 仅适用于minimax使用ChatCompletion Pro接口的模型 + minimaxGroupId string `required:"false" yaml:"minimaxGroupId" json:"minimaxGroupId"` // @Title zh-CN 模型名称映射表 // @Description zh-CN 用于将请求中的模型名称映射为目标AI服务商支持的模型名称。支持通过“*”来配置全局映射 modelMapping map[string]string `required:"false" yaml:"modelMapping" json:"modelMapping"` @@ -180,6 +185,7 @@ func (c *ProviderConfig) FromJson(json gjson.Result) { c.hunyuanAuthId = json.Get("hunyuanAuthId").String() c.hunyuanAuthKey = json.Get("hunyuanAuthKey").String() + c.minimaxGroupId = json.Get("minimaxGroupId").String() } func (c *ProviderConfig) Validate() error {