From 0173861a5aaa9e92519263a1f5685850712a3e14 Mon Sep 17 00:00:00 2001 From: hezhaoqian Date: Tue, 6 Oct 2026 13:09:51 +0800 Subject: [PATCH] fix(channel-monitor): build zhipu probe URL from the configured endpoint The zhipu adapter always appended the official /api/paas/v4/chat/completions path, so probes broke whenever the endpoint was not the official root: - OpenAI-compatible relay (e.g. https://relay.example.com): requested /api/paas/v4/chat/completions, which relays do not serve -> 404 - Coding Plan base (https://open.bigmodel.cn/api/coding/paas/v4): produced .../coding/paas/v4/api/paas/v4/chat/completions -> 404 Resolve the path from the endpoint instead: append /chat/completions when it already carries /paas/v4, keep the official path for bigmodel.cn / z.ai roots, and fall back to /v1/chat/completions (same as kimi/deepseek/minimax) for everything else. Fixes #7067 Co-Authored-By: Claude Opus 5.5 --- .../service/channel_monitor_checker.go | 33 ++++++++++++++++++- .../service/channel_monitor_endpoint_test.go | 19 +++++++++++ 2 files changed, 51 insertions(+), 1 deletion(-) diff --git a/backend/internal/service/channel_monitor_checker.go b/backend/internal/service/channel_monitor_checker.go index a310288bf9..ddb5e78317 100644 --- a/backend/internal/service/channel_monitor_checker.go +++ b/backend/internal/service/channel_monitor_checker.go @@ -297,7 +297,7 @@ func callProvider(ctx context.Context, provider, endpoint, apiKey, model, prompt return "", "", 0, err } headers := mergeHeaders(adapter.buildHeaders(apiKey), opts) - full := joinURL(endpoint, adapter.buildPath(model)) + full := joinURL(endpoint, monitorRequestPath(provider, endpoint, adapter, model)) respBytes, status, err := postRawJSON(ctx, full, body, headers) if err != nil { return "", "", status, err @@ -556,6 +556,37 @@ func postRawJSON(ctx context.Context, fullURL string, payload []byte, headers ma return respBody, resp.StatusCode, nil } +// monitorRequestPath 返回探测请求路径。智谱按 endpoint 区分: +// - 已带 /paas/v4(含 Coding Plan 的 /api/coding/paas/v4):只追加 /chat/completions +// - 官方域名根地址:/api/paas/v4/chat/completions +// - 中转站 / 本站网关:只暴露 OpenAI 兼容的 /v1/chat/completions,与 Kimi / DeepSeek 一致 +func monitorRequestPath(provider, endpoint string, adapter providerAdapter, model string) string { + if provider != MonitorProviderZhipu { + return adapter.buildPath(model) + } + u, err := url.Parse(strings.TrimSpace(endpoint)) + if err != nil { + return providerOpenAIPath + } + if strings.Contains(u.EscapedPath(), "/paas/v4") { + return "/chat/completions" + } + if isZhipuOfficialHost(u) { + return adapter.buildPath(model) + } + return providerOpenAIPath +} + +func isZhipuOfficialHost(u *url.URL) bool { + host := strings.ToLower(u.Hostname()) + for _, official := range []string{"bigmodel.cn", "z.ai"} { + if host == official || strings.HasSuffix(host, "."+official) { + return true + } + } + return false +} + // joinURL 保留 base 的上游路径前缀,并避免重复追加已有的 API 路径前缀。 // 使用 EscapedPath 匹配完整路径段,避免把 hostname 或编码斜杠当作路径。 func joinURL(base, path string) string { diff --git a/backend/internal/service/channel_monitor_endpoint_test.go b/backend/internal/service/channel_monitor_endpoint_test.go index 91482dd84f..7bb95aaa19 100644 --- a/backend/internal/service/channel_monitor_endpoint_test.go +++ b/backend/internal/service/channel_monitor_endpoint_test.go @@ -84,6 +84,9 @@ func TestCallProvider_BasePath(t *testing.T) { {"openai responses version", MonitorProviderOpenAI, MonitorAPIModeResponses, "/relay/v1", "/relay/v1/responses"}, {"gemini version", MonitorProviderGemini, "", "/relay/v1beta", "/relay/v1beta/models/test-model:generateContent"}, {"zhipu version", MonitorProviderZhipu, "", "/api/paas/v4", "/api/paas/v4/chat/completions"}, + {"zhipu coding plan", MonitorProviderZhipu, "", "/api/coding/paas/v4", "/api/coding/paas/v4/chat/completions"}, + {"zhipu relay origin", MonitorProviderZhipu, "", "", "/v1/chat/completions"}, + {"zhipu relay version", MonitorProviderZhipu, "", "/relay/v1", "/relay/v1/chat/completions"}, } { t.Run(tc.name, func(t *testing.T) { _, _, status, err := callProvider(context.Background(), tc.provider, @@ -94,3 +97,19 @@ func TestCallProvider_BasePath(t *testing.T) { }) } } + +func TestMonitorRequestPath_Zhipu(t *testing.T) { + adapter := providerAdapters[MonitorProviderZhipu] + for endpoint, want := range map[string]string{ + "https://open.bigmodel.cn": providerZhipuPath, + "https://api.z.ai": providerZhipuPath, + "https://open.bigmodel.cn/api/coding/paas/v4": "/chat/completions", + "https://birdapi.up.railway.app": providerOpenAIPath, + "https://notbigmodel.cn": providerOpenAIPath, + "https://relay.example.com/v1": providerOpenAIPath, + } { + require.Equal(t, want, monitorRequestPath(MonitorProviderZhipu, endpoint, adapter, "glm-5.3-flash"), endpoint) + } + require.Equal(t, providerOpenAIPath, + monitorRequestPath(MonitorProviderDeepseek, "https://birdapi.up.railway.app", providerAdapters[MonitorProviderDeepseek], "m")) +}