From 6e3b1d0a7f936ad6a65f14d6171b8d29a80a57d9 Mon Sep 17 00:00:00 2001 From: yc111233 Date: Fri, 10 Apr 2026 02:22:37 +0800 Subject: [PATCH] fix: filter empty documents in OpenAI rerank client Rerank providers like DashScope (qwen3-rerank) return HTTP 400 when any document in the batch is an empty string. This can happen when vector records have empty abstract fields. Fix: filter out empty/whitespace-only documents before sending to the rerank API, and map scores back to original indices (empty documents receive a score of 0.0). This acts as a safety net so that rerank degrades gracefully instead of failing entirely. Co-Authored-By: Claude Opus 4.6 --- openviking/models/rerank/openai_rerank.py | 43 ++++++++++++++++++----- 1 file changed, 34 insertions(+), 9 deletions(-) diff --git a/openviking/models/rerank/openai_rerank.py b/openviking/models/rerank/openai_rerank.py index 0760cb339..ec300890b 100644 --- a/openviking/models/rerank/openai_rerank.py +++ b/openviking/models/rerank/openai_rerank.py @@ -55,10 +55,26 @@ def rerank_batch(self, query: str, documents: List[str]) -> Optional[List[float] if not documents: return [] + # Filter out empty documents — rerank providers (e.g. DashScope) reject + # empty strings with HTTP 400. + valid_indices = [i for i, d in enumerate(documents) if d and d.strip()] + if not valid_indices: + return [0.0] * len(documents) + + if len(valid_indices) < len(documents): + filtered_docs = [documents[i] for i in valid_indices] + logger.debug( + "[OpenAIRerankClient] Filtered %d empty documents from %d total", + len(documents) - len(valid_indices), + len(documents), + ) + else: + filtered_docs = documents + req_body = { "model": self.model_name, "query": query, - "documents": documents, + "documents": filtered_docs, } try: @@ -75,7 +91,7 @@ def rerank_batch(self, query: str, documents: List[str]) -> Optional[List[float] result = response.json() # Update token usage tracking (estimate, OpenAI rerank doesn't provide token info) - self._extract_and_update_token_usage(result, query, documents) + self._extract_and_update_token_usage(result, query, filtered_docs) # Standard OpenAI/Cohere rerank format: results[].{index, relevance_score} results = result.get("results") @@ -83,26 +99,35 @@ def rerank_batch(self, query: str, documents: List[str]) -> Optional[List[float] logger.warning(f"[OpenAIRerankClient] Unexpected response format: {result}") return None - if len(results) != len(documents): + if len(results) != len(filtered_docs): logger.warning( "[OpenAIRerankClient] Unexpected rerank result length: expected=%s actual=%s", - len(documents), + len(filtered_docs), len(results), ) return None - # Results may not be in original order — sort by index - scores = [0.0] * len(documents) + # Map scores back to original document indices. + # Empty documents get a score of 0.0. + filtered_scores = [0.0] * len(filtered_docs) for item in results: idx = item.get("index") - if idx is None or not (0 <= idx < len(documents)): + if idx is None or not (0 <= idx < len(filtered_docs)): logger.warning( "[OpenAIRerankClient] Out-of-bounds or missing index in result: %s", item ) return None - scores[idx] = item.get("relevance_score", 0.0) + filtered_scores[idx] = item.get("relevance_score", 0.0) - logger.debug(f"[OpenAIRerankClient] Reranked {len(documents)} documents") + scores = [0.0] * len(documents) + for fi, oi in enumerate(valid_indices): + scores[oi] = filtered_scores[fi] + + logger.debug( + "[OpenAIRerankClient] Reranked %d documents (%d non-empty)", + len(documents), + len(valid_indices), + ) return scores except Exception as e: