perf(cloud): pass-through fast path for /v1/embeddings (#15512) - #15520
Conversation
Forward qualifying embeddings requests (direct-OpenAI source) verbatim and return the upstream JSON untouched — no AI-SDK decode/validate/re-encode of the float arrays. Usage parses once from the same buffer and bills through the identical settle chain; upstream failures throw the same APICallError shape the SDK path produces so the route catch maps them and releases the credit hold. Gated behind INFERENCE_PASSTHROUGH_EMBEDDINGS (default off; staging+production true, same soak discipline as #15437). Measured on prod 2026-07-08: gateway 1.14-3.83s vs direct 0.22-0.28s for the same call — 2-3 embedding calls per agent turn make this the largest remaining cloud-turn latency item.
|
Claude encountered an error —— View job I'll analyze this and get back to you. |
[qa-agent] money-bar review — APPROVE (post-merge confirmation; PR merged while review ran)Adversarial review — PR #15520 (embeddings pass-through, #15512) — MERGED into developTest counts (all run isolated via
|
| Suite | Result |
|---|---|
embeddings-passthrough.test.ts (new) |
7/7 pass |
| 5 sibling embeddings suites (affiliate-clamp 5, affiliate-reserve 5, credit-leak 7, optimistic-billing 10, route-billing 7) | 34/34 pass |
chat-completions-passthrough-streaming.test.ts |
28/28 pass |
| My adversarial probes (written fresh, 6 cases) | 6/6 pass |
PR-claimed counts (7 + 34 + 28) reproduce exactly.
The #15437 bar — item by item
- Usage extraction is REAL.
packages/cloud/api/v1/embeddings/route.ts:492-496: body buffered once viaarrayBuffer(),usage.prompt_tokensparsed from a decode of that same buffer, the identical buffer returned to the client. No tee needed (non-streaming), no double body read (text()only on the!okbranch). - Failure shapes settle fail-safe — probed live, not just read:
- upstream 429 → 429
rate_limit_exceeded,reconcile(0)(PR test) - upstream 500 → 503
provider_error,reconcile(0)(PR test) - 200 + unparseable JSON (untested by the PR) → JSON.parse throws → outer catch (
!billed) → hold released,billUsagenever called, ≥500 to client — confirmed by my probe - fetch network rejection (untested by the PR) → ≥500, hold released, nothing billed — confirmed by my probe
- missing usage /
prompt_tokens: 0→ bills the chars/4 estimate, never zero (PR test + my probe). Estimate can under-bill CJK, but that fallback is byte-identical to the SDK path'sresult.usage?.tokens || estimatedInputTokens— not a regression, and OpenAI always returns usage in practice.
- upstream 429 → 429
- Exactly-once settle. Pass-through slots between the existing reserve and the existing
settleBilling; samecreateCreditReservationSettlerfirst-call-wins owner, samesettlerBackedReservationview intobillUsage(affiliate collected-earnings clamp fix(cloud): pay affiliate earnings only from collected markup #11976/money: affiliate markup mints cashable earnings on uncollectable-overage settle via /v1/embeddings (reserve omits affiliateCode — #11972 residual missed by #11976) #12017 stays armed), samebilledguard. Upstream failure throwsAPICallErrorbeforebilled = true, so the route catch is the single release path. - Flag gating. New flag
INFERENCE_PASSTHROUGH_EMBEDDINGS(not the streaming flag),inference-passthrough.ts:47-49, same=== "true"trim pattern. Flag-off is byte-identical (only diff:let embeddingsinitialized to[], both SDK branches assign it; deadif (passthroughBody)skipped). wrangler.toml: top-level default"false"(line 178), staging"true"(402), production"true"(560) — identical shape to the sibling streaming flag's current state, meaning prod goes live on next deploy with no separate cutover PR (PR body states this; rollback = flip off). - Qualifying gate.
isPassthroughEmbeddingsEnabled() && resolveEmbeddingProviderSource() === "openai"+ resolver null-check.resolveEmbeddingProviderSourcereturns "openai" iffOPENAI_API_KEYset — exactly mirroringgetTextEmbeddingModel's first branch (same key, samenormalizeOpenAIModelId), so routing parity holds for every model id. Gateway source stays SDK (tested). 402 reserve is fail-closed before the forward (reserve block precedes the fetch structurally). Affiliate leg unchanged (sharedbillUsagewithaffiliateCode; affiliate requests already forced onto the synchronous reserve).
Embeddings-specific checks
- (a) Billing direction: real
prompt_tokensbilled; estimate only on absent/zero usage, same as SDK path. Never free (probed). - (b) Batch: one forward, one
billUsagewith the batch's singleprompt_tokens, oneusageService.create— no per-item multiply (my probe,inputTokens: 11for 3 inputs). - (c) 402:
reserveCreditsthrowsInsufficientCreditsError→ 402 before any upstream byte. Fail-closed. - (d) Agent callers: both real callers verified compatible with verbatim OpenAI JSON.
plugins/plugin-elizacloud/src/models/embeddings.ts(priority-50 cloud slot, the 2-3-calls-per-turn path) parsesdata[].embeddingfloats + per-batchindex+usage.{prompt,total}_tokens, never sendsencoding_format→ verbatim floats satisfy every check including the width gate.plugins/plugin-embeddingslikewise. Upstream headers are stripped (my probe:openai-organization/x-request-id/set-cookieall absent — route builds a fresh 2-header Response).
Confirmed defects
None money-path. Zero scenarios found where credits leak, double-settle, or inference goes free.
Flag-gated behavioral changes (data, not blockers — all fail-loud and money-safe)
dimensionsis now honored (route.ts:479-482forwards{...request}; the SDK path silently dropped it —embed/embedManycalled with model+value only).plugin-elizacloudalways sendsdimensions(embeddings.ts:207). Default config (text-embedding-3-small + 1536 = native width) → identical output. Divergent configs: (i) a model rejecting the param (text-embedding-ada-002 + any dimensions) flips from working → upstream 400 → 503 → handler throws → provider fall-through (hold released, unbilled); (ii) 3-small/3-large at non-native width (e.g. 384) flips from always-threw (native width returned, width check failed → fell to local embeddings) → now succeeds at the requested width — this is the designed fix(agent): cloud containers use cloud embeddings, not local gte-small (dim mismatch + boot waste) #8769 fix per the plugin's own comment, but an agent with existing fallback-provider vectors at that same width gets a silent embedding-space mix on recall. Triggered by a billing-flag deploy, not a config change.encoding_formatnow honored — explicit base64 requesters get base64 (previously ignored → floats). Official OpenAI SDKs handle both; no in-repo caller sends it.- Response
modelfield is OpenAI's canonical id, not an echo of the decorated request id (normalization verified:openai/text-embedding-3-small→text-embedding-3-small, spread cannot clobber it — my probe). - Single fetch, no AI-SDK retry on the pass-through leg — a transient upstream 5xx surfaces as an immediate 503 instead of ~2 retries; both agent callers retry 503 themselves. Latency-positive, same property the merged chat sibling has.
Verdict
Approve (post-merge confirmation). Money path matches the #15437 bar on all five criteria; the two untested failure shapes (200-with-bad-JSON, network rejection) were probed and settle fail-safe through the existing catch. The dimensions/encoding_format verbatim-forward changes (item 1-2 above) are the only client-visible deltas and are confined to non-default configs; worth one line in the staging-soak checklist: watch for 503 provider_error spikes from any org configured with ada-002 + explicit dimensions after the prod deploy.
Scratch worktree removed; /home/nubs/Git/wt-elevenlabs-dep left clean at 5523874.
— [qa-agent] every money PR in the latency wave now independently reviewed. @vps-backend one soak-checklist ask: watch for 503 provider_error spikes from any org configured ada-002 + explicit dimensions after the prod deploy (the dimensions-now-honored delta).
Closes #15512.
Summary
/api/v1/embeddings: whenINFERENCE_PASSTHROUGH_EMBEDDINGSis on and the direct-OpenAI source serves the model, forward the validated request verbatim and return the upstream JSON untouched — no AI-SDK decode/validate/re-encode of the float arraysbillUsage→ settler →usageService) bills exactly what the provider reported — identical to the SDK pathAPICallErrorshape the SDK path produces, so the existing route catch maps them (429/402/503) and releases the credit hold — one failure path, no new error handlingX-Eliza-Inference-Path: passthroughresponse header for probes, same convention as/v1/chat/completions(perf(cloud): opt-in pass-through streaming for openai-compatible providers (8x gateway overhead) #15437)[env.staging.vars]+[env.production.vars]set true — same soak-then-cutover shape as chore(cloud): enable pass-through streaming on production #15473; rollback = flip offWhy
Measured on prod 2026-07-08 (same input): gateway
/v1/embeddings1.14–3.83s vs direct api.openai.com 0.22–0.28s. Agent runtimes make 2–3 embedding calls per message turn (the always-on recall provider embeds every incoming message), so this is currently the single largest remaining cloud-agent latency item post-#15437/#15508 — worth 2–10s per turn.Tests
New
embeddings-passthrough.test.ts(7 cases, mirrors the sibling suites' module-boundary harness; the flag/qualification/error-mapping/hold-release logic runs real):usage.prompt_tokensviawaitUntilrate_limit_exceeded+ hold released (reconcile(0))provider_error+ hold releasedOPENAI_BASE_URLoverride respectedAll 5 sibling embeddings suites (34 tests) + the chat pass-through suite (28) green.
biome checkclean on touched files.Evidence: prod before/after latency table is in #15512 (live measurements, this branch not yet deployed anywhere — staging soak will produce the after-numbers once merged).
🤖 Generated with Claude Code