fix(gateway): thread asymmetric input_type to ZeroEntropy via request header — AI-SDK adapter drops providerOptions.openaiCompatible.input_type#2083
Closed
victorfteha wants to merge 1 commit into
Conversation
… header providerOptions.openaiCompatible.input_type is silently dropped by the AI-SDK adapter (unrecognized field; dimensions survives because it's a known param). Every embed request — including query-side embedQuery() — reached the ZE compat shim without input_type and got the shim's correct-for-ingest 'document' default. Net effect: the entire vector arm performed asymmetric retrieval with symmetric (document-typed) query vectors. Nothing errored; rankings were just quietly wrong. Fix: embedSubBatch() threads the input type as an 'x-gbrain-input-type' request header (headers survive the adapter); zeroEntropyCompatFetch consumes + strips it during its existing body rewrite. Race-free, no module state, no API change. The Voyage path is unaffected (it builds its request body directly, not via the openai-compatible adapter). Adds test/ze-input-type-wire.test.ts — drives embed()/embedQuery() through the REAL adapter via a new __setZeFetchForTests terminal-fetch seam and asserts the final wire body. The existing asymmetric-encoding-contract test captures providerOptions UPSTREAM of the adapter, so it stayed green throughout this bug; the new test fails without the fix (verified).
garrytan
added a commit
that referenced
this pull request
Jun 12, 2026
…SDK (supersedes #1400) Reviewed: gateway.ts AsyncLocalStorage + search/mode.ts; disjoint from authored fixes. Chosen over #2083 (whole openai-compatible recipe class vs ZeroEntropy-only). Credit @billy-armstrong for the original #1400 diagnosis. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Owner
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
embedQuery()exists and threadsinputType: 'query'intoproviderOptions.openaiCompatible— but the AI-SDK's openai-compatible adapter silently drops unrecognized providerOptions fields.dimensionssurvives (known param);input_typedoesn't. Every embed request therefore reached the ZeroEntropy compat shim without aninput_type, and the shim's correct-for-ingest default ('document') applied to queries too.Net effect: the entire vector arm performs asymmetric retrieval with symmetric (document-typed) query vectors. Nothing errors — rankings are just quietly wrong, especially for natural-question phrasings against note-style passages (exactly what asymmetric encoding exists to fix).
How this was found (evidence chain)
On a real ~2.3k-page / ~9.7k-chunk corpus (zembed-1 @ 1280, Postgres engine):
gbrain's ownembedQuery()output matched that document-typed vector, not the query-typed one.Worth noting:
test/asymmetric-encoding-contract.test.tsstayed green through all of this — it capturesproviderOptionsat the__setEmbedTransportForTestsseam, upstream of the adapter that drops the field. The contract it pins is real but doesn't reach the wire.Fix
embedSubBatch()threads the input type as anx-gbrain-input-typerequest header — headers survive the adapter, travel with the request (race-free), and need no module state.zeroEntropyCompatFetchconsumes + strips the header during its existing body rewrite and writesinput_typeinto the request body.input_typealready reaches the wire there).Test
New
test/ze-input-type-wire.test.tsdrivesembed()/embedQuery()through the real adapter via a new__setZeFetchForTeststerminal-fetch seam (mirrors the existing__set*ForTestsconventions) and asserts the final wire body:embedQuery(...)→ bodyinput_type: 'query', threading header stripped, URL rewrite ranembed([...])→ bodyinput_type: 'document'Verified to fail without the fix (the query-side assertion sees
'document').bun run verify: 30/30 green. Full unit suite: no new failures vs master — a set of provider-env-sensitive serial tests (reranker/cross-modal/think-gateway) fails identically on a clean master checkout in my environment (host injectsANTHROPIC_*env vars), so I'm expecting CI's clean env to be green.Operational note for existing installs
Any cached query-embedding artifacts produced while the bug was live are document-typed — after upgrading, clear the query cache and restart long-running
gbrain serveprocesses so they pick up the fixed code.Possibly related: #1469 reports the hybrid vector path returning identical results regardless of input (ollama). Different symptom — a fixed/ignored query vector vs. a wrong-typed one — so this PR does not claim to fix it, but it's the same subsystem and worth re-testing against this change.