Skip to content

fix(llm): complete path-less OpenAI-compatible base URLs - #5

Closed
jtguzman wants to merge 1 commit into
mainfrom
fix/lmstudio-openai-base-url
Closed

fix(llm): complete path-less OpenAI-compatible base URLs#5
jtguzman wants to merge 1 commit into
mainfrom
fix/lmstudio-openai-base-url

Conversation

@jtguzman

Copy link
Copy Markdown
Owner

Problem

A stored local base URL of http://host:port made the OpenAI SDK POST to /chat/completions instead of /v1/chat/completions — the SDK appends the path relative to base_url.

The failure was silent in a way worth spelling out: LM Studio answers the wrong path with HTTP 200 and a body of {"error":"Unexpected endpoint or method. (POST /chat/completions)"}. Nothing raises, so the agent reports AI generated empty response instead of a routing mistake.

The credential validator could not catch it either — model listing rides LM Studio's native WebSocket API (ws://host:port/llm), which is happy without the /v1 suffix, so the URL validated green.

Fix

normalize_openai_base_url in services/llm/config.py completes the URL only when it carries no path, so an explicit gateway path such as /openai or /api/v2 is left alone. One implementation, applied at two points:

  • services/llm/providers/openai.py — at the point of use, which makes an already-stored URL work without the operator re-entering it.
  • nodes/model/_local_validator.py — when the URL is persisted, so new entries are stored canonically.

Verification

Live against an LM Studio server at host:1234 with no /v1 stored: the same provider path that returned an empty response now returns a completion (base_url -> http://host:1234/v1/, content 'ok', usage returned).

14 new tests in server/tests/llm/test_base_url_normalization.py cover bare host:port/v1, an already-/v1 URL unchanged, a gateway path unchanged, empty/None, that the provider applies the completion whether the URL arrives via proxy_url or base_url, and that plain OpenAI still passes no base_url at all.

Full backend suite: 2969 passed, 2 failed — both pre-existing test_skill_icon_resolution.py failures unrelated to this change.

Out of scope, noted

An HTTP 200 carrying an {"error": ...} body should surface as a real error rather than an empty response. That is a separate change in openai.py.

🤖 Generated with Claude Code

A stored local base URL of `http://host:port` made the OpenAI SDK POST to
`/chat/completions` instead of `/v1/chat/completions`, because the SDK
appends the path relative to `base_url`. LM Studio answers the wrong path
with HTTP 200 and an `{"error": ...}` body, so nothing raises and the
agent reports "AI generated empty response" rather than a routing
mistake. The credential validator could not catch it either: model
listing rides LM Studio's native WebSocket API, which is happy without
the `/v1` suffix.

`normalize_openai_base_url` (services/llm/config.py) completes the URL
only when it carries no path, so an explicit gateway path such as
`/openai` is left alone. It is applied at the point of use in
OpenAIProvider — which makes an already-stored URL work without the
operator re-entering it — and when the local validator persists the URL,
so new entries are stored canonically.

Verified live against an LM Studio server: the same provider path that
returned an empty response now returns a completion.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@jtguzman

Copy link
Copy Markdown
Owner Author

Superado por zeenie-ai#115, que lleva el mismo arreglo rebasado sobre zeenie-ai/main en lugar del main del fork (esta rama arrastraba los 8 commits de Microsoft Graph que upstream todavía no tiene). Cierro y borro la rama; el tip queda archivado en el tag archivo/fix-lmstudio-openai-base-url.

@jtguzman jtguzman closed this Aug 17, 2026
@jtguzman
jtguzman deleted the fix/lmstudio-openai-base-url branch August 17, 2026 14:03
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.

1 participant