apache/shenyu · error · IllegalArgumentException

baseUrl must not be empty

Error message

baseUrl must not be empty

What it means

AiProxyPlugin.createOpenAiApi builds an OpenAiApi client from the plugin's AiCommonConfig and requires a non-null, non-empty baseUrl. When the configured AI provider base URL is missing the plugin throws IllegalArgumentException instead of constructing a client that could not reach any upstream.

Solutions

  1. Set baseUrl in the AI proxy plugin config (dashboard selector/rule or matching config source) to the provider endpoint, e.g. https://api.openai.com.
  2. Re-publish the plugin config so the gateway cache picks up the corrected value.
  3. Add a config validation step in admin/publishing that rejects empty baseUrl before it reaches the gateway.
  4. Check resolveFallbackContext's fallback config too — a valid primary config with an empty fallback baseUrl still triggers this.

Example fix

// before (admin config)
{"aiConfig": {"apiKey":"sk-...", "baseUrl":""}}
// after
{"aiConfig": {"apiKey":"sk-...", "baseUrl":"https://api.openai.com"}}
Defensive patterns

Strategy: validation

Validate before calling

function validateAiConfig(cfg) {
  if (!cfg || typeof cfg.baseUrl !== 'string' || cfg.baseUrl.trim() === '') {
    throw new Error('AI proxy config: baseUrl must not be empty');
  }
}

Type guard

function hasBaseUrl(cfg) {
  return typeof cfg?.baseUrl === 'string' && cfg.baseUrl.trim().length > 0;
}

Try / catch

try {
  OpenAiApi api = plugin.createOpenAiApi(config);
} catch (IllegalArgumentException e) {
  throw new IllegalStateException("AI proxy misconfigured: " + e.getMessage());
}

Prevention

When it happens

Trigger: The AI proxy selector/rule config (AiCommonConfig.baseUrl) is null or empty string when createOpenAiApi is invoked from getCachedOpenAiApi or resolveFallbackContext (e.g. on a cache miss or fallback resolution).

Common situations: Operator created the selector/rule in the ShenYu dashboard but left the baseUrl field blank; config sync delivered a partially filled AiCommonConfig; switching providers and clearing the URL without re-saving.

Understand the failure class

Background: "must not be empty", "cannot be empty" — required-field validation errors across open-source libraries — this error's family across 41 libraries.

Related errors


AI-assisted analysis of apache/shenyu@567142e072 (2026-09-12). Data as JSON: /api/errors/073701c4858a2c89. Report an issue: GitHub.

Appendix: source

Thrown at shenyu-plugin/shenyu-plugin-ai/shenyu-plugin-ai-proxy/src/main/java/org/apache/shenyu/plugin/ai/proxy/enhanced/AiProxyPlugin.java:245

    private OpenAiApi getCachedOpenAiApi(final String selectorId, final String type, final AiCommonConfig config) {
        final String cacheKey = selectorId + "|" + type + "_" + generateConfigCacheKey(config);
        return OpenAiApiCache.getInstance().computeIfAbsent(cacheKey, () -> createOpenAiApi(config));
    }

    private int generateConfigCacheKey(final AiCommonConfig config) {
        return Objects.hash(
                config.getBaseUrl(),
                config.getApiKey(),
                config.getModel(),
                config.getTemperature(),
                config.getMaxTokens()
        );
    }

    private OpenAiApi createOpenAiApi(final AiCommonConfig config) {
        if (Objects.isNull(config.getBaseUrl()) || config.getBaseUrl().isEmpty()) {
            throw new IllegalArgumentException("baseUrl must not be empty");
        }
        if (Objects.isNull(config.getApiKey()) || config.getApiKey().isEmpty()) {
            throw new IllegalArgumentException("apiKey must not be empty");
        }
        return OpenAiApi.builder()
                .baseUrl(config.getBaseUrl())
                .apiKey(config.getApiKey())
                .build();
    }

    @Override
    public int getOrder() {
        return PluginEnum.AI_PROXY.getCode();
    }

    @Override
    public String named() {
        return PluginEnum.AI_PROXY.getName();

View on GitHub (pinned to 567142e072)