Skip to content

fix(ai): tolerate trailing /v1 in custom model base URL to avoid /v1/v1 requests - #1889

Merged
openai0229 merged 1 commit into
OtterMind:mainfrom
solitude-mix:fix/openai-base-url-double-v1
Jul 21, 2026
Merged

openai0229 merged 1 commit into
OtterMind:mainfrom
solitude-mix:fix/openai-base-url-double-v1

Conversation

@solitude-mix

Copy link
Copy Markdown
Contributor

Fixes #1888

What changed

  • AiModelConfigServiceImpl.resolveRuntimeModel (the single place AiRuntimeModel is built, covering saved configs, request overrides, and the OPENAI_BASE_URL/ANTHROPIC_BASE_URL env fallbacks) now strips a trailing /v1 (and trailing slashes) from the base URL for the OPENAI and CLAUDE providers.
  • The connection test (testOpenAiCompatibleConfig) now builds the exact URL the runtime requests (normalized base + /v1/chat/completions), so a passing test no longer masks a failing chat request.
  • The ai upstream target log in AiModelFactory now prints the URL actually requested (it previously logged base + /chat/completions while the real request was base + /v1/chat/completions).
  • The Base URL hint in all five locales now says the trailing /v1 is optional and never duplicated.
  • New AiModelConfigServiceImplBaseUrlTest covers both providers, prefixed gateways (.../compatible-mode/v1), and non-/v1 suffixes (.../v1beta stays untouched).

Why

The settings UI instructs users to include /v1 in the Base URL, and "Test connection" honors that contract (baseUrl + "/chat/completions"). But the actual chat request goes through Spring AI's OpenAiApi, whose default completionsPath is /v1/chat/completions (Spring AI 1.1.4). Following the UI hint therefore produces .../v1/v1/chat/completions404 Not Found, while the connection test passes. AnthropicApi has the same trap (/v1/messages).

Stripping the trailing /v1 — rather than setting completionsPath("/chat/completions") — was chosen deliberately: it fixes configs that follow the UI hint without breaking configs that already work today with a base URL that omits /v1, and it keeps prefixed endpoints like https://dashscope.aliyuncs.com/compatible-mode/v1 working.

How I tested it

  • mvn -pl :chat2db-community-domain-core -am -Dtest='AiModelConfigServiceImpl*' -Dmaven.test.skip=false -DskipTests=false -Dsurefire.failIfNoSpecifiedTests=false -Dmaven.test.failure.ignore=false test → 19 tests, 0 failures (new BaseUrlTest 7 + existing StorageTest 12).
  • mvn -pl :chat2db-community-web -am ... test → compiles clean (main + test sources).
  • Manually reproduced before the fix: Base URL with /v1 → 404 at .../v1/v1/chat/completions; without /v1 → works.
  • Frontend change is locale-string-only; yarn lint / build:web:community were not run locally.

Known limitations

Spring AI's OpenAiApi/AnthropicApi append version-prefixed paths
(/v1/chat/completions, /v1/messages) to the configured base URL, while
the settings UI asked users to include /v1 in the Base URL. Custom
OpenAI-compatible models therefore requested .../v1/v1/chat/completions
and failed with 404, even though the connection test passed.

Normalize the resolved runtime base URL by stripping a trailing /v1 so
base URLs with and without it hit the same endpoint, make the
connection test request the same URL as the runtime, fix the
upstream-target log to print the real URL, and update the Base URL
hints in all locales.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Custom OpenAI-compatible model requests .../v1/v1/chat/completions (404) when Base URL includes /v1 as instructed

2 participants