diff --git a/plugins/wasm-go/extensions/ai-proxy/README.md b/plugins/wasm-go/extensions/ai-proxy/README.md index 21817ebee..d664d10e7 100644 --- a/plugins/wasm-go/extensions/ai-proxy/README.md +++ b/plugins/wasm-go/extensions/ai-proxy/README.md @@ -134,6 +134,10 @@ Groq 所对应的 `type` 为 `groq`。它并无特有的配置字段。 文心一言所对应的 `type` 为 `baidu`。它并无特有的配置字段。 +#### 360智脑 + +360智脑所对应的 `type` 为 `ai360`。它并无特有的配置字段。 + #### MiniMax MiniMax所对应的 `type` 为 `minimax`。它特有的配置字段如下: @@ -940,6 +944,77 @@ provider: } ``` +### 使用 OpenAI 协议代理360智脑服务 + +**配置信息** + +```yaml +provider: + type: ai360 + apiTokens: + - "YOUR_MINIMAX_API_TOKEN" + modelMapping: + "gpt-4o": "360gpt-turbo-responsibility-8k" + "gpt-4": "360gpt2-pro" + "gpt-3.5": "360gpt-turbo" + "*": "360gpt-pro" +``` + +**请求示例** + +```json +{ + "model": "gpt-4o", + "messages": [ + { + "role": "system", + "content": "你是一个专业的开发人员!" + }, + { + "role": "user", + "content": "你好,你是谁?" + } + ] +} +``` + +**响应示例** + +```json +{ + "choices": [ + { + "message": { + "role": "assistant", + "content": "你好,我是360智脑,一个大型语言模型。我可以帮助回答各种问题、提供信息、进行对话等。有什么可以帮助你的吗?" + }, + "finish_reason": "", + "index": 0 + } + ], + "created": 1724257207, + "id": "5e5c94a2-d989-40b5-9965-5b971db941fe", + "model": "360gpt-turbo", + "object": "", + "usage": { + "completion_tokens": 33, + "prompt_tokens": 24, + "total_tokens": 57 + }, + "messages": [ + { + "role": "system", + "content": "你是一个专业的开发人员!" + }, + { + "role": "user", + "content": "你好,你是谁?" + } + ], + "context": null +} +``` + ### 使用 OpenAI 协议代理 Cloudflare Workers AI 服务 **配置信息** diff --git a/plugins/wasm-go/extensions/ai-proxy/provider/ai360.go b/plugins/wasm-go/extensions/ai-proxy/provider/ai360.go new file mode 100644 index 000000000..c9f68710b --- /dev/null +++ b/plugins/wasm-go/extensions/ai-proxy/provider/ai360.go @@ -0,0 +1,74 @@ +package provider + +import ( + "errors" + "github.com/higress-group/proxy-wasm-go-sdk/proxywasm" + "github.com/higress-group/proxy-wasm-go-sdk/proxywasm/types" + + "github.com/alibaba/higress/plugins/wasm-go/extensions/ai-proxy/util" + "github.com/alibaba/higress/plugins/wasm-go/pkg/wrapper" +) + +// ai360Provider is the provider for 360 OpenAI service. +const ( + ai360Domain = "api.360.cn" +) + +type ai360ProviderInitializer struct { +} + +type ai360Provider struct { + config ProviderConfig + contextCache *contextCache +} + +func (m *ai360ProviderInitializer) ValidateConfig(config ProviderConfig) error { + if config.apiTokens == nil || len(config.apiTokens) == 0 { + return errors.New("no apiToken found in provider config") + } + return nil +} + +func (m *ai360ProviderInitializer) CreateProvider(config ProviderConfig) (Provider, error) { + return &ai360Provider{ + config: config, + contextCache: createContextCache(&config), + }, nil +} + +func (m *ai360Provider) GetProviderType() string { + return providerTypeAi360 +} + +func (m *ai360Provider) OnRequestHeaders(ctx wrapper.HttpContext, apiName ApiName, log wrapper.Log) (types.Action, error) { + if apiName != ApiNameChatCompletion { + return types.ActionContinue, errUnsupportedApiName + } + _ = util.OverwriteRequestHost(ai360Domain) + _ = proxywasm.RemoveHttpRequestHeader("Accept-Encoding") + _ = proxywasm.RemoveHttpRequestHeader("Content-Length") + _ = proxywasm.ReplaceHttpRequestHeader("Authorization", m.config.GetRandomToken()) + // Delay the header processing to allow changing streaming mode in OnRequestBody + return types.HeaderStopIteration, nil +} + +func (m *ai360Provider) OnRequestBody(ctx wrapper.HttpContext, apiName ApiName, body []byte, log wrapper.Log) (types.Action, error) { + if apiName != ApiNameChatCompletion { + return types.ActionContinue, errUnsupportedApiName + } + request := &chatCompletionRequest{} + if err := decodeChatCompletionRequest(body, request); err != nil { + return types.ActionContinue, err + } + if request.Model == "" { + return types.ActionContinue, errors.New("missing model in chat completion request") + } + // 映射模型 + mappedModel := getMappedModel(request.Model, m.config.modelMapping, log) + if mappedModel == "" { + return types.ActionContinue, errors.New("model becomes empty after applying the configured mapping") + } + ctx.SetContext(ctxKeyFinalRequestModel, mappedModel) + request.Model = mappedModel + return types.ActionContinue, replaceJsonRequestBody(request, log) +} diff --git a/plugins/wasm-go/extensions/ai-proxy/provider/provider.go b/plugins/wasm-go/extensions/ai-proxy/provider/provider.go index 76b488e7a..f446874ee 100644 --- a/plugins/wasm-go/extensions/ai-proxy/provider/provider.go +++ b/plugins/wasm-go/extensions/ai-proxy/provider/provider.go @@ -19,6 +19,7 @@ const ( providerTypeMoonshot = "moonshot" providerTypeAzure = "azure" + providerTypeAi360 = "ai360" providerTypeQwen = "qwen" providerTypeOpenAI = "openai" providerTypeGroq = "groq" @@ -73,6 +74,7 @@ var ( providerInitializers = map[string]providerInitializer{ providerTypeMoonshot: &moonshotProviderInitializer{}, providerTypeAzure: &azureProviderInitializer{}, + providerTypeAi360: &ai360ProviderInitializer{}, providerTypeQwen: &qwenProviderInitializer{}, providerTypeOpenAI: &openaiProviderInitializer{}, providerTypeGroq: &groqProviderInitializer{}, @@ -235,13 +237,12 @@ func (c *ProviderConfig) FromJson(json gjson.Result) { } } c.targetLang = json.Get("targetLang").String() - + if schemaValue, ok := json.Get("responseJsonSchema").Value().(map[string]interface{}); ok { c.responseJsonSchema = schemaValue } else { - c.responseJsonSchema = nil + c.responseJsonSchema = nil } - c.customSettings = make([]CustomSetting, 0) customSettingsJson := json.Get("customSettings")