From 07982e20a4a5a1bc4211d11c7d2cdd2c2e4f1b45 Mon Sep 17 00:00:00 2001 From: RerankerGuo <121015044+RerankerGuo@users.noreply.github.com> Date: Tue, 28 Jul 2026 16:50:11 +0800 Subject: [PATCH] fix: resolve openai-compat provider fails when using IP:port base URL MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Closes #1057 - Bug 1: Detect IP vs domain host and use static service source for IPs instead of DNS, preventing Envoy DNS cluster resolution failures - Bug 2: Strip port from openaiCustomUrl (scheme://host/path only) to avoid polluting the upstream Host header and host-domain matching - Bug 3: Implement GET→PUT if exists / POST if not idempotent updates for ensureServiceSource, ensureStaticServiceSource, and EnsureAIProvider so re-running the initializer correctly updates existing configs - Apply same fixes to setup-higress.sh shell script Test: gofmt -e (syntax check passed), bash -n (shell syntax OK) --- agentteams-controller/go.mod | 2 +- .../internal/gateway/higress.go | 39 +++++++++++ .../internal/initializer/initializer.go | 64 ++++++++++++++++--- changelog/current.md | 1 + manager/scripts/init/setup-higress.sh | 32 ++++++++-- 5 files changed, 122 insertions(+), 16 deletions(-) diff --git a/agentteams-controller/go.mod b/agentteams-controller/go.mod index 0a7e339a4..3d5b22183 100644 --- a/agentteams-controller/go.mod +++ b/agentteams-controller/go.mod @@ -1,6 +1,6 @@ module github.com/agentscope-ai/AgentTeams/agentteams-controller -go 1.25.0 +go 1.25 require ( github.com/alibabacloud-go/apig-20240327/v6 v6.0.6 diff --git a/agentteams-controller/internal/gateway/higress.go b/agentteams-controller/internal/gateway/higress.go index e53bb2574..245c157fe 100644 --- a/agentteams-controller/internal/gateway/higress.go +++ b/agentteams-controller/internal/gateway/higress.go @@ -407,6 +407,19 @@ func (c *HigressClient) EnsureAIProvider(ctx context.Context, req AIProviderRequ if req.Raw != nil { body["rawConfigs"] = req.Raw } + + _, sc, _ := c.doJSON(ctx, http.MethodGet, "/v1/ai/providers/"+req.Name, nil) + if sc == http.StatusOK { + _, putSC, err := c.doJSON(ctx, http.MethodPut, "/v1/ai/providers/"+req.Name, body) + if err != nil { + return fmt.Errorf("update AI provider %s: %w", req.Name, err) + } + if putSC != http.StatusOK && putSC != http.StatusCreated { + return fmt.Errorf("update AI provider %s: HTTP %d", req.Name, putSC) + } + return nil + } + _, sc, err := c.doJSON(ctx, http.MethodPost, "/v1/ai/providers", body) if err != nil { return fmt.Errorf("ensure AI provider %s: %w", req.Name, err) @@ -677,6 +690,19 @@ func (c *HigressClient) ensureServiceSource(ctx context.Context, name, dnsDomain "properties": map[string]interface{}{}, "authN": map[string]interface{}{"enabled": false}, } + + _, sc, _ := c.doJSON(ctx, http.MethodGet, "/v1/service-sources/"+name, nil) + if sc == http.StatusOK { + _, putSC, err := c.doJSON(ctx, http.MethodPut, "/v1/service-sources/"+name, body) + if err != nil { + return fmt.Errorf("update service source %s: %w", name, err) + } + if putSC != http.StatusOK && putSC != http.StatusCreated { + return fmt.Errorf("update service source %s: HTTP %d", name, putSC) + } + return nil + } + _, sc, err := c.doJSON(ctx, http.MethodPost, "/v1/service-sources", body) if err != nil { return fmt.Errorf("ensure service source %s: %w", name, err) @@ -694,6 +720,19 @@ func (c *HigressClient) ensureStaticServiceSource(ctx context.Context, name, add "properties": map[string]interface{}{}, "authN": map[string]interface{}{"enabled": false}, } + + _, sc, _ := c.doJSON(ctx, http.MethodGet, "/v1/service-sources/"+name, nil) + if sc == http.StatusOK { + _, putSC, err := c.doJSON(ctx, http.MethodPut, "/v1/service-sources/"+name, body) + if err != nil { + return fmt.Errorf("update static service source %s: %w", name, err) + } + if putSC != http.StatusOK && putSC != http.StatusCreated { + return fmt.Errorf("update static service source %s: HTTP %d", name, putSC) + } + return nil + } + _, sc, err := c.doJSON(ctx, http.MethodPost, "/v1/service-sources", body) if err != nil { return fmt.Errorf("ensure static service source %s: %w", name, err) diff --git a/agentteams-controller/internal/initializer/initializer.go b/agentteams-controller/internal/initializer/initializer.go index b8f3c402f..655e9ac15 100644 --- a/agentteams-controller/internal/initializer/initializer.go +++ b/agentteams-controller/internal/initializer/initializer.go @@ -3,6 +3,7 @@ package initializer import ( "context" "fmt" + "net" "net/url" "strconv" "strings" @@ -331,7 +332,7 @@ func (i *Initializer) initGatewayRoutes(ctx context.Context) error { logger.Error(err, "failed to create LLM provider (non-fatal)") } } else { - // Parse URL to create DNS service source + // Parse URL to determine host type and create appropriate service source host, port, err := parseHostPort(cfg.OpenAIBaseURL) if err != nil { logger.Error(err, "failed to parse AGENTTEAMS_OPENAI_BASE_URL (non-fatal)") @@ -340,15 +341,37 @@ func (i *Initializer) initGatewayRoutes(ctx context.Context) error { if strings.HasPrefix(cfg.OpenAIBaseURL, "http://") { proto = "http" } - if err := i.Gateway.EnsureServiceSource(ctx, "openai-compat", host, port, proto); err != nil { - logger.Error(err, "failed to register openai-compat service source (non-fatal)") + + var svcSuffix string + if net.ParseIP(host) != nil { + // IP address → static-type service source + if err := i.Gateway.EnsureStaticServiceSource(ctx, "openai-compat", host, port); err != nil { + logger.Error(err, "failed to register openai-compat static service source (non-fatal)") + } + svcSuffix = "static" + } else { + // Domain name → DNS-type service source + if err := i.Gateway.EnsureServiceSource(ctx, "openai-compat", host, port, proto); err != nil { + logger.Error(err, "failed to register openai-compat service source (non-fatal)") + } + svcSuffix = "dns" } - // Wait for DNS service source to propagate before creating provider time.Sleep(2 * time.Second) + + // Strip port from openaiCustomUrl — only keep scheme://host/path + u, err := url.Parse(cfg.OpenAIBaseURL) + customUrl := cfg.OpenAIBaseURL + if err == nil { + customUrl = fmt.Sprintf("%s://%s%s", u.Scheme, u.Hostname(), u.Path) + if u.RawQuery != "" { + customUrl += "?" + u.RawQuery + } + } + raw := map[string]interface{}{ "agentteamsMode": true, - "openaiCustomUrl": cfg.OpenAIBaseURL, - "openaiCustomServiceName": "openai-compat.dns", + "openaiCustomUrl": customUrl, + "openaiCustomServiceName": "openai-compat." + svcSuffix, "openaiCustomServicePort": port, } if err := i.Gateway.EnsureAIProvider(ctx, gateway.AIProviderRequest{ @@ -375,14 +398,35 @@ func (i *Initializer) initGatewayRoutes(ctx context.Context) error { if strings.HasPrefix(cfg.OpenAIBaseURL, "http://") { proto = "http" } - if err := i.Gateway.EnsureServiceSource(ctx, provider, host, port, proto); err != nil { - logger.Error(err, "failed to register service source for provider (non-fatal)") + + var svcSuffix string + if net.ParseIP(host) != nil { + if err := i.Gateway.EnsureStaticServiceSource(ctx, provider, host, port); err != nil { + logger.Error(err, "failed to register static service source for provider (non-fatal)") + } + svcSuffix = "static" + } else { + if err := i.Gateway.EnsureServiceSource(ctx, provider, host, port, proto); err != nil { + logger.Error(err, "failed to register service source for provider (non-fatal)") + } + svcSuffix = "dns" } time.Sleep(2 * time.Second) + + // Strip port from openaiCustomUrl — only keep scheme://host/path + u, err := url.Parse(cfg.OpenAIBaseURL) + customUrl := cfg.OpenAIBaseURL + if err == nil { + customUrl = fmt.Sprintf("%s://%s%s", u.Scheme, u.Hostname(), u.Path) + if u.RawQuery != "" { + customUrl += "?" + u.RawQuery + } + } + raw := map[string]interface{}{ "agentteamsMode": true, - "openaiCustomUrl": cfg.OpenAIBaseURL, - "openaiCustomServiceName": provider + ".dns", + "openaiCustomUrl": customUrl, + "openaiCustomServiceName": provider + "." + svcSuffix, "openaiCustomServicePort": port, } if err := i.Gateway.EnsureAIProvider(ctx, gateway.AIProviderRequest{ diff --git a/changelog/current.md b/changelog/current.md index 6436a6d7d..58d1f7388 100644 --- a/changelog/current.md +++ b/changelog/current.md @@ -6,6 +6,7 @@ Record image-affecting changes to `manager/`, `worker/`, `copaw/`, `hermes/`, `o **Bug Fixes** +- **openai-compat IP:port support**: Fix openai-compat provider to use static service source for IP addresses (instead of DNS), strip port from openaiCustomUrl, and use GET→PUT idempotent updates for service sources and AI providers. Fixes #1057. - **Team Worker room boundary convergence**: Remove Manager again after standalone Worker infrastructure reconciliation restores regular Team Worker personal-room membership. ([b5b0add](https://github.com/agentscope-ai/AgentTeams/commit/b5b0add)) - **Team Worker reference enforcement**: Keep referenced Worker CRs protected during direct deletion and reject Team API members whose required role is empty. ([d96f1ed](https://github.com/agentscope-ai/AgentTeams/commit/d96f1ed)) - **Team Worker room membership**: Force Manager out of regular Team Worker personal rooms when equal Matrix power levels prevent a normal kick. ([43545c2](https://github.com/agentscope-ai/AgentTeams/commit/43545c2)) diff --git a/manager/scripts/init/setup-higress.sh b/manager/scripts/init/setup-higress.sh index 0d84e61d0..02c3338ab 100755 --- a/manager/scripts/init/setup-higress.sh +++ b/manager/scripts/init/setup-higress.sh @@ -209,16 +209,38 @@ if [ -n "${AGENTTEAMS_LLM_API_KEY}" ]; then OC_DOMAIN="${OC_URL_STRIP%%/*}" echo "${OC_DOMAIN}" | grep -q ':' && { OC_PORT="${OC_DOMAIN##*:}"; OC_DOMAIN="${OC_DOMAIN%:*}"; } + # Detect IP vs domain for service source type + OC_IS_IP=false + if echo "${OC_DOMAIN}" | grep -qE '^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$'; then + OC_IS_IP=true + fi + + # Strip port from openaiCustomUrl: keep scheme://host/path only + OC_PATH="${OC_URL_STRIP#*/}" + [ "${OC_PATH}" = "/" ] && OC_PATH="" + OC_CUSTOM_URL="${OC_PROTO}://${OC_DOMAIN}${OC_PATH}" + # Service source: GET → PUT if exists, POST if not existing_svc=$(higress_get /v1/service-sources/openai-compat) - SVC_BODY='{"type":"dns","name":"openai-compat","port":'"${OC_PORT}"',"protocol":"'"${OC_PROTO}"'","proxyName":"","domain":"'"${OC_DOMAIN}"'"}' - if [ -n "${existing_svc}" ]; then - higress_api PUT /v1/service-sources/openai-compat "Updating openai-compat DNS service source" "${SVC_BODY}" + if [ "${OC_IS_IP}" = "true" ]; then + SVC_BODY='{"type":"static","name":"openai-compat","port":'"${OC_PORT}"',"protocol":"http","proxyName":"","domain":"'"${OC_DOMAIN}:${OC_PORT}"'"}' + if [ -n "${existing_svc}" ]; then + higress_api PUT /v1/service-sources/openai-compat "Updating openai-compat static service source" "${SVC_BODY}" + else + higress_api POST /v1/service-sources "Registering openai-compat static service source" "${SVC_BODY}" + fi + OC_SVC_SUFFIX="static" else - higress_api POST /v1/service-sources "Registering openai-compat DNS service source" "${SVC_BODY}" + SVC_BODY='{"type":"dns","name":"openai-compat","port":'"${OC_PORT}"',"protocol":"'"${OC_PROTO}"'","proxyName":"","domain":"'"${OC_DOMAIN}"'"}' + if [ -n "${existing_svc}" ]; then + higress_api PUT /v1/service-sources/openai-compat "Updating openai-compat DNS service source" "${SVC_BODY}" + else + higress_api POST /v1/service-sources "Registering openai-compat DNS service source" "${SVC_BODY}" + fi + OC_SVC_SUFFIX="dns" fi - PROVIDER_BODY='{"type":"openai","name":"openai-compat","tokens":["'"${AGENTTEAMS_LLM_API_KEY}"'"],"version":0,"protocol":"openai/v1","tokenFailoverConfig":{"enabled":false},"rawConfigs":{"openaiCustomUrl":"'"${OPENAI_BASE_URL}"'","openaiCustomServiceName":"openai-compat.dns","openaiCustomServicePort":'"${OC_PORT}"',"agentteamsMode":true}}' + PROVIDER_BODY='{"type":"openai","name":"openai-compat","tokens":["'"${AGENTTEAMS_LLM_API_KEY}"'"],"version":0,"protocol":"openai/v1","tokenFailoverConfig":{"enabled":false},"rawConfigs":{"openaiCustomUrl":"'"${OC_CUSTOM_URL}"'","openaiCustomServiceName":"openai-compat.'"${OC_SVC_SUFFIX}"'","openaiCustomServicePort":'"${OC_PORT}"',"agentteamsMode":true}}' existing_provider=$(higress_get /v1/ai/providers/openai-compat) if [ -n "${existing_provider}" ]; then higress_api PUT /v1/ai/providers/openai-compat "Updating LLM provider (openai-compat)" "${PROVIDER_BODY}"