embeddings: default check_embedding_ctx_length=False for OpenAI-compa… - #129
embeddings: default check_embedding_ctx_length=False for OpenAI-compa…#129kkampli-singlestore wants to merge 5 commits into
Conversation
…tible endpoints langchain OpenAIEmbeddings defaults to check_embedding_ctx_length=True, which tokenizes client-side with tiktoken and sends OpenAI token IDs. Non-OpenAI models behind an OpenAI-compatible endpoint (e.g. Qwen) can't interpret those IDs and return nonsensical embeddings. Default the flag to False so raw text is sent and the server tokenizes correctly. No-op for genuine OpenAI models; callers can opt back in with check_embedding_ctx_length=True. Scoped to the OpenAI path, so the Bedrock/Amazon branch is unaffected. Co-authored-by: Cursor <cursoragent@cursor.com>
Only default check_embedding_ctx_length=False for self-hosted / non-OpenAI models
(e.g. Qwen on the 'Nova' platform). Genuine OpenAI/Azure models ('Azure'/'OpenAI')
keep langchain's default (True), preserving correct tiktoken tokenization and
client-side long-input chunking for them.
Co-authored-by: Cursor <cursoragent@cursor.com>
…models - Azure (OpenAI) models: keep check_embedding_ctx_length=True so langchain uses tiktoken (correct for these models) with the passed model name, incl. its long- input chunking. - Non-OpenAI models (e.g. Qwen on 'Nova'): send raw text (check_embedding_ctx_length =False) so the server's own tokenizer is used, and split long inputs into character-bounded chunks client-side, embedding each and length-weighted-averaging them into one vector per input -- irrespective of the flag -- so long texts never hit the server's hard context limit (which otherwise errors or silently truncates). Output stays 1:1 with input rows; short inputs pass through unchanged. Co-authored-by: Cursor <cursoragent@cursor.com>
…False otherwise Non-OpenAI models (e.g. Qwen on 'Nova') get wrong embeddings from langchain's default tiktoken tokenization (it sends OpenAI token IDs). Send raw text for them so the server tokenizes with the model's own tokenizer; keep tiktoken (True) for Azure/OpenAI models, where it's correct. Long-input chunking deferred. Co-authored-by: Cursor <cursoragent@cursor.com>
Re-add _ChunkedOpenAIEmbeddings: for non-Azure models (e.g. Qwen on 'Nova'), send raw text (check_embedding_ctx_length=False) and split long inputs into character-bounded chunks, embedding each and length-weighted-averaging into one vector per input, so long texts never hit the server's context limit. Azure keeps tiktoken (True). Co-authored-by: Cursor <cursoragent@cursor.com>
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using default effort and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, have a team admin enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 6def72b. Configure here.
| n = max(1, self.max_chunk_chars) | ||
| if len(text) <= n: | ||
| return [text] | ||
| return [text[i:i + n] for i in range(0, len(text), n)] |
There was a problem hiding this comment.
Character chunks exceed token limit
Medium Severity
_ChunkedOpenAIEmbeddings caps chunks at max_chunk_chars (24000), but Nova-style servers enforce a token context window (often 8192). For scripts where characters map roughly one-to-one to tokens, a single chunk can still exceed that limit, so the server may reject the request or truncate input while the client treats the embedding as complete.
Reviewed by Cursor Bugbot for commit 6def72b. Configure here.


Don’t use OpenAI’s tokenizer for Qwen — LangChain’s default (check_embedding_ctx_length=True) turns text into OpenAI token IDs. Qwen can’t read those, so embeddings are garbage. Send raw text instead (False) so the server tokenizes correctly.
Keep OpenAI/Azure on the default — Real OpenAI/Azure models still use True (tiktoken + LangChain’s built-in long-text handling). Only non-OpenAI/Nova models get the raw-text default.
Chunk long Qwen inputs ourselves — With raw text, LangChain won’t split long docs, so the server 400s or truncates. For those models, split into character chunks, embed each, then average into one vector so long text still works.
Test cases:
1.
Before:
label cosine_vs_anchor
anchor 1.0
unrelated2 0.3536
same_intent 0.3461
paraphrase 0.304
unrelated 0.2523
After

Chunking Long Text

Note
Medium Risk
Changes default embedding behavior and vector composition for non-Azure OpenAI-compatible models, which can alter embedding outputs and API call patterns for long documents; Azure and Amazon paths are explicitly preserved.
Overview
SingleStoreEmbeddingsFactorynow picks the embeddings client by hosting platform instead of always returning stockOpenAIEmbeddings.For Azure, behavior stays aligned with real OpenAI models:
check_embedding_ctx_length=Trueand LangChain’s tiktoken-based client-side handling.For other OpenAI-compatible hosts (e.g. Qwen on Nova), the factory defaults
check_embedding_ctx_length=Falseso requests send raw text for server-side tokenization, and returns a new_ChunkedOpenAIEmbeddingswrapper. That class splits inputs longer thanmax_chunk_chars(default 24k), embeds each chunk via sync/asyncembed_documents, then length-weighted averages chunk vectors and L2-normalizes them back to one vector per input so long text avoids server reject/truncate limits.The Bedrock/Amazon path is unchanged.
Reviewed by Cursor Bugbot for commit 6def72b. Bugbot is set up for automated code reviews on this repo. Configure here.