Skip to content

feat(router): provider-flexible /v1/embeddings with Azure Foundry path (A4) - #112

Merged
mrobinson2 merged 2 commits into
mainfrom
feat/a4-provider-flexible-embeddings
Jul 12, 2026
Merged

feat(router): provider-flexible /v1/embeddings with Azure Foundry path (A4)#112
mrobinson2 merged 2 commits into
mainfrom
feat/a4-provider-flexible-embeddings

Conversation

@mrobinson2

Copy link
Copy Markdown
Owner

What

Kills the memory system's hard OpenAI-billing dependency: the router's /v1/embeddings passthrough is now provider-flexible with a documented, end-to-end Azure AI Foundry deployment path — and it bakes in the load-bearing LiteLLM provider-detection pin so forks can't rediscover the 400 unknown_model failure mode. Ported from the upstream private deployment's incident learnings.

Design

  • openai/ LiteLLM prefix pin (_pin_embedding_provider, services/model-router/main.py) — when EMBEDDING_BASE_URL points at an azure.com host and the model string has no provider/ prefix, LiteLLM flips to its AZURE provider and authenticates with an api-key header; Foundry's OpenAI-compatible /openai/v1 endpoint rejects that with 400 unknown_model. The router now prefixes a bare EMBEDDING_MODEL with openai/ before the litellm.aembedding call, pinning detection to the Bearer-auth OpenAI-compatible path. An explicit provider/ prefix (e.g. azure/<deployment> for a classic Azure OpenAI resource) is honored unchanged.
  • Operator-facing vs wire model split_EMBED_MODEL stays the reported model name; only _EMBED_LITELLM_MODEL (pinned) goes upstream. Callers never see the routing prefix.
  • Fail loud, stay optional — unchanged semantics, now documented: 503 until EMBEDDING_API_KEY (or the OPENAI_API_KEY fallback) is set, 502 on upstream failure with the real error logged server-side only. No new required config; a clean fork boots exactly as before.
  • Config surface (env-driven, all optional): EMBEDDING_API_KEY, EMBEDDING_BASE_URL, EMBEDDING_MODEL (default text-embedding-3-small), EMBEDDING_TIMEOUT_SECONDS, EMBEDDING_MAX_INPUTS — wired through docker-compose.yml and .env.example.

Test evidence

pytest -q services/model-router/tests (same invocation as CI's model-router step): 200 passed locally on Python 3.13.

New/updated coverage in services/model-router/tests/test_embeddings.py (10 passed):

  • prefix pin: bare model → openai/text-embedding-3-small; explicit azure/... and openai/... honored without stacking; import-time default is pinned
  • Foundry-shaped end-to-end (mocked upstream): pinned model + azure.com api_base + key all reach litellm.aembedding
  • response model field stays unprefixed when the upstream omits it
  • fail-loud paths: 503 unconfigured, 400 missing/oversized input, 502 provider error

Docs

  • docs/walkthroughs/azure-foundry-embeddings.md — end to end: create the Foundry text-embedding-3-small deployment (portal + az CLI), set the three env vars, verify a 1536-dim vector with curl, and a troubleshooting section that explains the unknown_model mechanism, plus 503/502/wrong-dimension cases.
  • services/model-router/README.md — embeddings provider-config section + /v1/embeddings endpoint row.

🤖 Generated with Claude Code

Michael Robinson and others added 2 commits July 11, 2026 23:07
…refix pin (A4)

The /v1/embeddings passthrough now pins LiteLLM provider detection to the
OpenAI-compatible Bearer-auth path: a bare EMBEDDING_MODEL is prefixed with
openai/ before the litellm.aembedding call. Without the pin, an azure.com
EMBEDDING_BASE_URL flips LiteLLM to its AZURE provider (api-key header auth),
which Azure AI Foundry's /openai/v1 endpoint rejects with 400 unknown_model.
An explicit provider/ prefix is honored unchanged, so classic Azure OpenAI
resources (which genuinely want api-key auth) remain reachable.

Ported from the upstream private deployment's incident learnings. Callers keep
seeing the operator-facing model name; only the LiteLLM call carries the prefix.

Tests: prefix-pin unit coverage, mocked Foundry-style end-to-end (pinned model +
api_base + key reach litellm), response model field stays unprefixed, plus the
existing 503-unconfigured / 400 / 502 fail-loud paths.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- docs/walkthroughs/azure-foundry-embeddings.md: create the Foundry
  text-embedding-3-small deployment, configure EMBEDDING_BASE_URL /
  EMBEDDING_API_KEY / EMBEDDING_MODEL, verify a 1536-dim vector with curl,
  and troubleshoot 400 unknown_model / 503 / 502 / wrong-dimension cases.
- services/model-router/README.md: embeddings provider-config section +
  /v1/embeddings endpoint row, with the provider-detection pin note.
- .env.example + docker-compose.yml: EMBEDDING_* pass-through on the
  model-router service, off by default (endpoint answers 503 until keyed).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@mrobinson2
mrobinson2 merged commit 25e2753 into main Jul 12, 2026
11 checks passed
@mrobinson2
mrobinson2 deleted the feat/a4-provider-flexible-embeddings branch July 12, 2026 07:44
mrobinson2 added a commit that referenced this pull request Jul 12, 2026
…) (#118)

Six PRs merged to main since v1.7 (A1-A6: vendored incident-fix defaults,
agent-loop canary smoke, vendored-config schema guard, provider-flexible
embeddings, canonical user-peer identity, hard cost-envelope enforcement)
turn silent failures into loud ones across the vendored-app config surface,
the router, and the agent-loop itself. Document them honestly as merged-to-
main-but-unreleased in README's What's new/Roadmap and ROADMAP.md's new
v1.8 section, and clean up a stale Future-releases item the v1.5/A6 cost
governance work already supersedes.

Co-authored-by: Michael Robinson <michaelrobinson@Michaels-PC.local>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
mrobinson2 pushed a commit that referenced this pull request Jul 21, 2026
v1.7 hardened the security posture; applying that hardening to a real
subscription then broke the paths that install and update the platform. v1.8
collects the repairs and the guards that make the same class of breakage fail
loudly next time.

Adds docs/releases/v1.8.0.md (fresh-deploy firewall/PG15 fix, the paperclip
build-pin drift, the DSN username guard and its six-day outage, the gitleaks
allowlists, dependency bumps, upgrade notes), a README section framing it as a
repair release rather than a feature one, a ROADMAP "v1.8: shipped" section, and
the release badge.

The v1.7 tag was cut before the reliability-hardening merges (#112-#117) reached
main, so those are documented under v1.7 and contained in this tag; v1.8.0.md
covers only what is new since that documentation. Renames the horizon section to
post-1.8 and adds the still-open services/paperclip Python 3.14 bump (#124) to
it — its smoke job fails, so it is explicitly not in this release.

The notes describe the demo feature-flag false positive rather than quoting the
string: the allowlist that exempts it is scoped to docs/notes/, so reproducing
it here would fail the scan, and widening a security allowlist to accommodate
prose is the wrong trade.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
mrobinson2 added a commit that referenced this pull request Jul 21, 2026
…131)

v1.7 hardened the security posture; applying that hardening to a real
subscription then broke the paths that install and update the platform. v1.8
collects the repairs and the guards that make the same class of breakage fail
loudly next time.

Adds docs/releases/v1.8.0.md (fresh-deploy firewall/PG15 fix, the paperclip
build-pin drift, the DSN username guard and its six-day outage, the gitleaks
allowlists, dependency bumps, upgrade notes), a README section framing it as a
repair release rather than a feature one, a ROADMAP "v1.8: shipped" section, and
the release badge.

The v1.7 tag was cut before the reliability-hardening merges (#112-#117) reached
main, so those are documented under v1.7 and contained in this tag; v1.8.0.md
covers only what is new since that documentation. Renames the horizon section to
post-1.8 and adds the still-open services/paperclip Python 3.14 bump (#124) to
it — its smoke job fails, so it is explicitly not in this release.

The notes describe the demo feature-flag false positive rather than quoting the
string: the allowlist that exempts it is scoped to docs/notes/, so reproducing
it here would fail the scan, and widening a security allowlist to accommodate
prose is the wrong trade.

Co-authored-by: Michael Robinson <michaelrobinson@Michaels-PC.local>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
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