From 44d688a1684294d92f420526d882737eecf594d6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=BE=84=E6=BD=AD?= Date: Thu, 12 Feb 2026 22:19:13 +0800 Subject: [PATCH] feat(ai-proxy): add zhipu provider enhancements (#3488) --- .../extensions/ai-proxy/provider/provider.go | 8 ++++ .../extensions/ai-proxy/provider/zhipuai.go | 42 ++++++++++++++++--- 2 files changed, 44 insertions(+), 6 deletions(-) diff --git a/plugins/wasm-go/extensions/ai-proxy/provider/provider.go b/plugins/wasm-go/extensions/ai-proxy/provider/provider.go index c026dd6ee..d6eb72a0f 100644 --- a/plugins/wasm-go/extensions/ai-proxy/provider/provider.go +++ b/plugins/wasm-go/extensions/ai-proxy/provider/provider.go @@ -453,6 +453,12 @@ type ProviderConfig struct { // @Title zh-CN Claude Code 模式 // @Description zh-CN 仅适用于Claude服务。启用后将伪装成Claude Code客户端发起请求,支持使用Claude Code的OAuth Token进行认证。 claudeCodeMode bool `required:"false" yaml:"claudeCodeMode" json:"claudeCodeMode"` + // @Title zh-CN 智谱AI服务域名 + // @Description zh-CN 仅适用于智谱AI服务。默认为 open.bigmodel.cn(中国),可配置为 api.z.ai(国际) + zhipuDomain string `required:"false" yaml:"zhipuDomain" json:"zhipuDomain"` + // @Title zh-CN 智谱AI Code Plan 模式 + // @Description zh-CN 仅适用于智谱AI服务。启用后将使用 /api/coding/paas/v4/chat/completions 接口 + zhipuCodePlanMode bool `required:"false" yaml:"zhipuCodePlanMode" json:"zhipuCodePlanMode"` } func (c *ProviderConfig) GetId() string { @@ -658,6 +664,8 @@ func (c *ProviderConfig) FromJson(json gjson.Result) { c.vllmCustomUrl = json.Get("vllmCustomUrl").String() c.doubaoDomain = json.Get("doubaoDomain").String() c.claudeCodeMode = json.Get("claudeCodeMode").Bool() + c.zhipuDomain = json.Get("zhipuDomain").String() + c.zhipuCodePlanMode = json.Get("zhipuCodePlanMode").Bool() c.contextCleanupCommands = make([]string, 0) for _, cmd := range json.Get("contextCleanupCommands").Array() { if cmd.String() != "" { diff --git a/plugins/wasm-go/extensions/ai-proxy/provider/zhipuai.go b/plugins/wasm-go/extensions/ai-proxy/provider/zhipuai.go index c2e01bcf7..8268cf8c2 100644 --- a/plugins/wasm-go/extensions/ai-proxy/provider/zhipuai.go +++ b/plugins/wasm-go/extensions/ai-proxy/provider/zhipuai.go @@ -8,11 +8,15 @@ import ( "github.com/alibaba/higress/plugins/wasm-go/extensions/ai-proxy/util" "github.com/higress-group/proxy-wasm-go-sdk/proxywasm/types" "github.com/higress-group/wasm-go/pkg/wrapper" + "github.com/tidwall/gjson" + "github.com/tidwall/sjson" ) const ( - zhipuAiDomain = "open.bigmodel.cn" + zhipuAiDefaultDomain = "open.bigmodel.cn" + zhipuAiInternationalDomain = "api.z.ai" zhipuAiChatCompletionPath = "/api/paas/v4/chat/completions" + zhipuAiCodePlanPath = "/api/coding/paas/v4/chat/completions" zhipuAiEmbeddingsPath = "/api/paas/v4/embeddings" zhipuAiAnthropicMessagesPath = "/api/anthropic/v1/messages" ) @@ -26,16 +30,20 @@ func (m *zhipuAiProviderInitializer) ValidateConfig(config *ProviderConfig) erro return nil } -func (m *zhipuAiProviderInitializer) DefaultCapabilities() map[string]string { +func (m *zhipuAiProviderInitializer) DefaultCapabilities(codePlanMode bool) map[string]string { + chatPath := zhipuAiChatCompletionPath + if codePlanMode { + chatPath = zhipuAiCodePlanPath + } return map[string]string{ - string(ApiNameChatCompletion): zhipuAiChatCompletionPath, + string(ApiNameChatCompletion): chatPath, string(ApiNameEmbeddings): zhipuAiEmbeddingsPath, // string(ApiNameAnthropicMessages): zhipuAiAnthropicMessagesPath, } } func (m *zhipuAiProviderInitializer) CreateProvider(config ProviderConfig) (Provider, error) { - config.setDefaultCapabilities(m.DefaultCapabilities()) + config.setDefaultCapabilities(m.DefaultCapabilities(config.zhipuCodePlanMode)) return &zhipuAiProvider{ config: config, contextCache: createContextCache(&config), @@ -65,13 +73,35 @@ func (m *zhipuAiProvider) OnRequestBody(ctx wrapper.HttpContext, apiName ApiName func (m *zhipuAiProvider) TransformRequestHeaders(ctx wrapper.HttpContext, apiName ApiName, headers http.Header) { util.OverwriteRequestPathHeaderByCapability(headers, string(apiName), m.config.capabilities) - util.OverwriteRequestHostHeader(headers, zhipuAiDomain) + // Use configured domain or default to China domain + domain := m.config.zhipuDomain + if domain == "" { + domain = zhipuAiDefaultDomain + } + util.OverwriteRequestHostHeader(headers, domain) util.OverwriteRequestAuthorizationHeader(headers, "Bearer "+m.config.GetApiTokenInUse(ctx)) headers.Del("Content-Length") } +func (m *zhipuAiProvider) TransformRequestBody(ctx wrapper.HttpContext, apiName ApiName, body []byte) ([]byte, error) { + if apiName != ApiNameChatCompletion { + return m.config.defaultTransformRequestBody(ctx, apiName, body) + } + + // Check if reasoning_effort is set + reasoningEffort := gjson.GetBytes(body, "reasoning_effort").String() + if reasoningEffort != "" { + // Add thinking config for ZhipuAI + body, _ = sjson.SetBytes(body, "thinking", map[string]string{"type": "enabled"}) + // Remove reasoning_effort field as ZhipuAI doesn't recognize it + body, _ = sjson.DeleteBytes(body, "reasoning_effort") + } + + return m.config.defaultTransformRequestBody(ctx, apiName, body) +} + func (m *zhipuAiProvider) GetApiName(path string) ApiName { - if strings.Contains(path, zhipuAiChatCompletionPath) { + if strings.Contains(path, zhipuAiChatCompletionPath) || strings.Contains(path, zhipuAiCodePlanPath) { return ApiNameChatCompletion } if strings.Contains(path, zhipuAiEmbeddingsPath) {