Skip to content

Reject /v1 suffix on OpenAIProvider base_url#84

Merged
chris-colinsky merged 2 commits into
mainfrom
feature/openai-provider-base-url-validation
May 28, 2026
Merged

Reject /v1 suffix on OpenAIProvider base_url#84
chris-colinsky merged 2 commits into
mainfrom
feature/openai-provider-base-url-validation

Conversation

@chris-colinsky
Copy link
Copy Markdown
Member

Summary

Reject base_url values whose path ends in /v1 (with or without a trailing slash) at OpenAIProvider construction time. httpx joins base URLs by appending the request path, so a base_url like https://api.openai.com/v1 plus the provider's /v1/chat/completions request produces a doubled /v1/v1/chat/completions on the wire. Some backends (Bifrost was the motivating case from a downstream consumer) return 200 for GET /v1/v1/models but reject POST /v1/v1/chat/completions — the readiness probe goes green at lifespan startup and every completion silently 405s.

Strict ValueError rather than silent strip-and-warn keeps the misconfiguration visible at construction. Other non-empty paths (proxy prefixes like /api/openai-proxy or /v1/openai-proxy) are left alone for intentional reverse-proxy setups; only a trailing /v1 is rejected.

Behavior

base_url Result
https://api.openai.com accepted
http://localhost:8090/ accepted (trailing slash stripped)
https://api.openai.com/v1 raises ValueError
http://localhost:8090/v1/ raises ValueError
https://gateway.example.com/openai-proxy accepted (non-/v1 path)
https://gateway.example.com/v1/openai-proxy accepted (/v1 not trailing)

Test plan

  • 7 new unit tests in tests/unit/test_llm_provider.py cover each case
  • Full unit + conformance suite: 878 passed, 113 skipped
  • ruff check / ruff format --check clean
  • pyright clean on changed files
  • docs/model-providers/index.md fixed (was showing the rejected /v1 shape as canonical)

httpx joins base_url paths by appending, so an unprefixed /v1 on
base_url produces a doubled /v1/v1/... wire path that some backends
(Bifrost was the motivating case) accept on GET /v1/v1/models while
rejecting POST /v1/v1/chat/completions. Result: the readiness probe
stays green and every completion silently 405s.

Raise ValueError at construction time when base_url's path ends in
/v1 (with or without trailing slash) so the misconfiguration is
visible at lifespan startup, not inside per-call wire failures.
Trailing slashes are still normalized; other non-empty paths
(proxy prefixes like /api/openai-proxy) are left alone for
intentional reverse-proxy setups.

Also fix docs/model-providers/index.md which wrongly showed
base_url="http://localhost:8000/v1" as the canonical shape.
Copilot AI review requested due to automatic review settings May 28, 2026 00:03
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR tightens OpenAIProvider configuration by rejecting base_url values that would cause duplicated /v1 paths when the provider appends its own OpenAI-compatible endpoints.

Changes:

  • Adds base_url validation and normalization in OpenAIProvider.
  • Documents the required host-root base_url shape.
  • Adds unit tests for accepted and rejected base_url forms.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 1 comment.

File Description
src/openarmature/llm/providers/openai.py Adds validation helper, constructor integration, and provider docstring guidance.
tests/unit/test_llm_provider.py Adds unit coverage for /v1 rejection and accepted proxy/root URL cases.
docs/model-providers/index.md Updates the example provider configuration to use host root only.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/openarmature/llm/providers/openai.py Outdated
The previous validation called rstrip("/") on the full base_url
before parsing, which is a no-op when the URL ends in a query
string or fragment character (e.g., the 'c' in '?token=abc').
The parsed path's own trailing slash was then left intact, so
'/v1/' slipped past the endswith('/v1') check.

Strip trailing slashes from the parsed path itself before the
suffix check. Adds two unit tests covering the query-string and
fragment cases.
@chris-colinsky chris-colinsky merged commit ddafc18 into main May 28, 2026
6 checks passed
@chris-colinsky chris-colinsky deleted the feature/openai-provider-base-url-validation branch May 28, 2026 00:26
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.

2 participants