Repository navigation
Replies: 1 comment
|
Hi @ankekod, thanks for this. I read
Since this discussion hasn't had a maintainer reply yet, I've opened #344 to get it triaged, linking here and to #318. Are you planning to open a PR? If not, would you be OK with me opening one that builds on your commit (keeping you as the author), with the rename and the check as separate commits? Happy to go whichever way you prefer. |
0 replies
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Many popular embedding models are asymmetric: they need a signal telling
them whether a text is being embedded for storage (a document/passage) or
for retrieval (a query). Embedding both the same way measurably degrades
retrieval accuracy. This is indicated by the
input_typefield in the OpenAI API specification.rag_api currently embeds documents and queries identically and doesn't use the ìnput_type`field, so these models
can't be used correctly today:
input_typebody fieldpassagequeryinput_typebody fieldsearch_documentsearch_queryinput_typebody fielddocumentquerytaskbody fieldretrieval.passageretrieval.query"passage: ""query: "Proposal
No API or LibreChat change is needed. Internally the store-vs-retrieve intent
is already unambiguous:
/embedflows throughEmbeddings.embed_documents()and
/querythroughembed_query(). The change is confined to embeddingsinitialization in
app/config.pyplus one small wrapper implementing theLangChain
Embeddingsinterface.query embeds through an
AsymmetricEmbeddingswrapper that sends the rightsignal per operation (
embed_documents/embed_queryand async variants).model_kwargs={"extra_body": {...}}. Verified against the pinnedlangchain-openai/openai— the field lands at the top level of the/embeddingsrequest body, exactly what NIM and friends read.agnostic — works with Ollama, HF, TEI, etc., where body-field injection can't.
Two separate client instances back the two operations rather than mutating one
client's
model_kwargsper call:/queryembeds run in the thread pool while/embedbatches run concurrently, so a shared mutable instance would be a racecondition. In prefix-only mode the two instances would be byte-identical, so a
single client is shared between both slots — this avoids loading a local
sentence-transformers model into memory twice.
New env vars, all optional and fully backward compatible (default behavior is
unchanged — a plain client, no wrapper):
EMBEDDINGS_ASYMMETRIC=trueon a provider that can't inject body fields(anything but
openai/azure) fails fast at startup with an error pointing tothe prefix vars, rather than silently embedding every query as a passage.
Because configuration is env-vars-only, this is drop-in compatible with
existing deployment methods, including LibreChat's Helm chart (
rag.configEnv)and the lite image — no chart or Dockerfile changes needed.
Scope of changes
app/services/embeddings.py(~40 lines,AsymmetricEmbeddings)app/config.py— the new env vars, anextra_bodyparam oninit_embeddings, and abuild_embeddings()helper holding thewrapper/validation logic
in-process mock-server test asserting
input_typereaches the request bodyStatus
Implemented on my fork, on a branch off current
main:https://github.com/ankekod/rag_api/tree/new/feature/asymmetric-embeddings
Verified end-to-end in a live LibreChat deployment — LibreChat running
against this build of rag_api, pointed at an NVIDIA NIM asymmetric embedding
model. Uploading files (LibreChat →
/embed) sendsinput_type=passageandfile search (→
/query) sendsinput_type=query, both reaching NIM correctly,with retrieval working. This confirms the headline claim in practice: no
LibreChat-side change is needed — the store-vs-retrieve direction is fully
carried by which rag_api endpoint LibreChat already calls.
pytestpasses (246 tests). Happy to open a PR againstmainif this directionis acceptable.
Caveat we'd document
Enabling this changes how documents are embedded, so vectors stored beforehand
(without an
input_type/ prefix) should be re-embedded — querying old vectorswith the query-side setting compares two different input modes and degrades
results. Called out in the README and the PR description.
All reactions