Embedding Vector Validation#
RAGFlow's embedding pipeline enforces two invariants: (1) the number of vectors returned by an embedding provider must exactly match the number of input texts; (2) empty or whitespace-only inputs never reach a provider. Violations of these invariants silently corrupt the chunk→vector mapping, which causes incorrect retrieval at query time.
Pre-Processing: Filtering Empty Inputs#
Before any provider call, inputs are sanitized at two layers.
EmbeddingUtils._handle_whitespace() (shared utility, pipeline and task-executor paths) replaces any text that is empty or whitespace-only with the string "None" . This prevents providers from receiving blank strings, which some external APIs reject or return zero-length vectors for.
tokenizer.py _embedding() function (pipeline/canvas path) takes a stricter approach: it builds a valid_pairs list of (original_index, chunk) tuples, skipping any chunk whose concatenated, HTML-stripped text is empty . If all chunks are empty, it returns early without calling any provider . This was introduced in PR #14354 to replace the previous assert len(vects) == len(chunks) which would spuriously fail when some chunks had no embeddable content.
HTML table tags (<table>, <td>, <caption>, <tr>, <th>) are normalized to spaces before the whitespace check in both paths .
Count Validation: Vectors Must Match Inputs#
After embedding, both code paths explicitly check alignment:
-
tokenizer.pyassertslen(vects) == len(valid_pairs)and then writesq_{dim}_veconly onto the chunks invalid_pairs, leaving non-embedded chunks untouched . -
EmbeddingService.embed_chunks()(task-executor path) assertslen(vects) == len(docs)after stacking all batch results . The upstreamEmbeddingUtils.attach_vectors()raises aValueErrorif the lengths diverge .
Provider-Level: Handling Out-of-Order Responses#
OpenAI-compatible endpoints (including LiteLLM proxies) may return embedding items in an order that does not match the input batch order. RAGFlow addresses this with:
-
_sorted_by_index(items)— sorts SDK response objects by their.indexattribute . Used byOpenAIEmbed,LocalAIEmbed,ZhipuEmbed,XinferenceEmbed,OpenRouterEmbed, and others . -
Base._openai_http_embeddings(response)— for providers using rawrequestsHTTP calls, sorts thedatalist byd.get("index", 0). Used byNvidiaEmbedandSILICONFLOWEmbed.
Both strategies are stable no-ops when the provider returns items in order.
Provider-Level: Batching and Error Surface#
All OpenAI-style providers delegate to Base._batched_encode(), which:
- Optionally truncates each text to
truncate_totokens before sending. - Issues
ceil(len(texts) / batch_size)calls, accumulating vectors and token counts across batches. - Wraps any provider exception in a single
EmbeddingError, ensuring callers see a consistent exception type regardless of which SDK failed.
The standard token ceiling for most providers is DEFAULT_MAX_TOKENS = 8192 . Provider-specific overrides exist (e.g., Zhipu embedding-2: 512 tokens; Zhipu embedding-3: 3072 tokens ).
Known Edge Cases and Issues#
| Issue | Provider | Status |
|---|---|---|
encode_queries returning shape (1, D) instead of (D,) | BaiduYiyanEmbed | Reported #16397 , patched in PR #16398 |
| 404 on model connection test due to Azure deployment name vs. model name mismatch | AzureEmbed | Configuration issue; enter the Azure deployment name, not the model name |
| LiteLLM proxy and OpenAI-compatible endpoints returning items out of batch order | Many providers | Mitigated by _sorted_by_index / _openai_http_embeddings index sort |
The BaiduYiyanEmbed 2-D shape bug illustrates the adapter contract: encode_queries must return a 1-D vector of shape (D,), while encode() must return shape (N, D) . Any provider that returns the wrong shape will misalign downstream consumers silently.
Key Source Files#
| File | Purpose |
|---|---|
rag/llm/embedding_model.py | All provider adapters, _batched_encode, _sorted_by_index, EmbeddingError |
rag/svr/task_executor_refactor/embedding_utils.py | _handle_whitespace, attach_vectors, combine_title_content_vectors |
rag/svr/task_executor_refactor/embedding_service.py | Task-executor batch embedding + assert len(vects) == len(docs) |
rag/flow/tokenizer/tokenizer.py | Pipeline _embedding(), valid_pairs filter + count assertion |