fix(embedding): normalize gateway base_url to the /v1 base path

EMBEDDING_GATEWAY_URL is configured as a bare origin (scheme://host:port) —
the deployment's Service URL. GatewayProvider now appends the gateway's /v1
base path before handing the URL to the OpenAI SDK, so both embed posts
({base}/embeddings) and dimension discovery ({base}/models) land under /v1.
Idempotent: a URL already ending in /v1 is left unchanged.

This lets EMBEDDING_GATEWAY_URL stay a bare domain (matching the gitops
Service URLs) instead of requiring a hand-appended /v1.

Also align the `embedding_gateway_model` field default with _DEFAULTS
("mistral/mistral-embed"). The gateway catalog is provider-namespaced, and
_detect_dimension matches `entry.id == embedding_model`; the stale
un-namespaced default would silently miss the catalog entry and leave the
dimension unresolved (re-triggering the external-mode startup crash).

Tests: bare / trailing-slash / idempotent normalization + a bare-origin
discovery test asserting /v1/models. 16 gateway-provider tests pass;
providers + vector suites green (129 total); ruff clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Chris Coutinho
2026-06-01 17:39:23 +02:00
co-authored by Claude Opus 4.8
parent 2d191b4055
commit 4f170d32cb
3 changed files with 71 additions and 3 deletions
@@ -289,3 +289,59 @@ async def test_detect_dimension_skips_when_already_known(monkeypatch):
await provider._detect_dimension()
assert called["n"] == 0
assert provider.get_dimension() == 1024
# --- /v1 base-path normalization --------------------------------------------
# EMBEDDING_GATEWAY_URL is configured as a bare origin (scheme://host:port);
# the provider appends the gateway's /v1 base path so both the OpenAI SDK's
# embed posts ({base}/embeddings) and discovery ({base}/models) land under /v1.
def _client_base(provider: GatewayProvider) -> str:
return str(provider.client.base_url).rstrip("/")
def test_bare_base_url_gets_v1_base_path():
provider = GatewayProvider(
base_url="http://gw:8083", embedding_model="mistral/mistral-embed"
)
assert _client_base(provider).endswith("/v1")
def test_v1_base_url_is_idempotent():
# A URL that already carries /v1 (e.g. legacy config) is not doubled.
provider = GatewayProvider(
base_url="http://gw:8083/v1", embedding_model="mistral/mistral-embed"
)
base = _client_base(provider)
assert base.endswith("/v1")
assert not base.endswith("/v1/v1")
def test_trailing_slash_base_url_normalized():
provider = GatewayProvider(
base_url="http://gw:8083/", embedding_model="mistral/mistral-embed"
)
base = _client_base(provider)
assert base.endswith("/v1")
assert not base.endswith("/v1/v1")
async def test_detect_dimension_with_bare_base_url_hits_v1_models(monkeypatch):
"""End-to-end of the fix: a bare-origin base_url still resolves the
dimension because discovery lands on /v1/models."""
seen = {}
def handler(request: httpx.Request) -> httpx.Response:
seen["url"] = str(request.url)
return httpx.Response(
200, json={"data": [{"id": "mistral/mistral-embed", "dimension": 1024}]}
)
_mock_async_client(monkeypatch, handler)
provider = GatewayProvider(
base_url="http://gw:8083", embedding_model="mistral/mistral-embed"
)
await provider._detect_dimension()
assert provider.get_dimension() == 1024
assert seen["url"].endswith("/v1/models")