{
  "id": "llm/rag-hybrid-search-score-mismatch",
  "signature": "RAG hybrid search returns no results because dense and sparse scores are on different scales",
  "signature_zh": "RAG 混合搜索返回空结果，因为稠密和稀疏分数不在同一尺度上",
  "regex": "RAG hybrid search returns no results.*dense and sparse scores.*different scales",
  "domain": "llm",
  "category": "data_error",
  "subcategory": null,
  "root_cause": "When using hybrid search (dense + sparse), the dense embedding similarity scores (e.g., cosine similarity 0.0-1.0) and sparse BM25/SPLADE scores (e.g., 0-20) are not normalized to the same range before fusion, causing one component to dominate or return no results after thresholding.",
  "root_cause_type": "generic",
  "root_cause_zh": "当使用混合搜索（稠密 + 稀疏）时，稠密嵌入相似度分数（例如，余弦相似度 0.0-1.0）和稀疏 BM25/SPLADE 分数（例如，0-20）在融合前未归一化到同一范围，导致一个组件占主导地位或在阈值处理后返回空结果。",
  "versions": [
    {
      "version": "langchain==0.2.5",
      "introduced": null,
      "deprecated": null,
      "removed": null,
      "behavior_change": null,
      "status": "active"
    },
    {
      "version": "chromadb==0.5.0",
      "introduced": null,
      "deprecated": null,
      "removed": null,
      "behavior_change": null,
      "status": "active"
    },
    {
      "version": "qdrant-client==1.9.0",
      "introduced": null,
      "deprecated": null,
      "removed": null,
      "behavior_change": null,
      "status": "active"
    },
    {
      "version": "elasticsearch==8.13.0",
      "introduced": null,
      "deprecated": null,
      "removed": null,
      "behavior_change": null,
      "status": "active"
    }
  ],
  "os_specific": {},
  "dead_ends": [
    {
      "action": "Lowering the similarity threshold to 0.0 to force results",
      "why_fails": "This returns all documents regardless of relevance, defeating the purpose of hybrid search and polluting the context with irrelevant documents.",
      "fail_rate": 0.8,
      "condition": "",
      "sources": []
    },
    {
      "action": "Switching to only dense search",
      "why_fails": "This eliminates the sparse component, which may be crucial for keyword-based retrieval in domains like legal or medical where specific terms matter.",
      "fail_rate": 0.65,
      "condition": "",
      "sources": []
    },
    {
      "action": "Increasing the alpha weight for the sparse component to 0.9",
      "why_fails": "This does not solve the scale mismatch; it only shifts dominance. The scores are still on different scales, so the fusion remains skewed.",
      "fail_rate": 0.7,
      "condition": "",
      "sources": []
    }
  ],
  "workarounds": [
    {
      "action": "Normalize both score sets to [0,1] using min-max scaling before fusion. Example:\n\ndef normalize_scores(scores):\n    min_s, max_s = min(scores), max(scores)\n    if max_s == min_s:\n        return [0.0] * len(scores)\n    return [(s - min_s) / (max_s - min_s) for s in scores]\n\ndense_scores = normalize_scores(dense_scores)\nsparse_scores = normalize_scores(sparse_scores)\nhybrid_scores = [alpha * d + (1-alpha) * s for d, s in zip(dense_scores, sparse_scores)]",
      "success_rate": 0.9,
      "how": "Normalize both score sets to [0,1] using min-max scaling before fusion. Example:\n\ndef normalize_scores(scores):\n    min_s, max_s = min(scores), max(scores)\n    if max_s == min_s:\n        return [0.0] * len(scores)\n    return [(s - min_s) / (max_s - min_s) for s in scores]\n\ndense_scores = normalize_scores(dense_scores)\nsparse_scores = normalize_scores(sparse_scores)\nhybrid_scores = [alpha * d + (1-alpha) * s for d, s in zip(dense_scores, sparse_scores)]",
      "condition": "",
      "sources": []
    },
    {
      "action": "Use a rank-based fusion (RRF) instead of score-based fusion. RRF combines ranks directly, avoiding scale issues entirely. Example:\n\ndef reciprocal_rank_fusion(dense_ranks, sparse_ranks, k=60):\n    scores = {}\n    for rank, doc_id in enumerate(dense_ranks):\n        scores[doc_id] = 1 / (k + rank + 1)\n    for rank, doc_id in enumerate(sparse_ranks):\n        scores[doc_id] = scores.get(doc_id, 0) + 1 / (k + rank + 1)\n    return sorted(scores.items(), key=lambda x: x[1], reverse=True)",
      "success_rate": 0.95,
      "how": "Use a rank-based fusion (RRF) instead of score-based fusion. RRF combines ranks directly, avoiding scale issues entirely. Example:\n\ndef reciprocal_rank_fusion(dense_ranks, sparse_ranks, k=60):\n    scores = {}\n    for rank, doc_id in enumerate(dense_ranks):\n        scores[doc_id] = 1 / (k + rank + 1)\n    for rank, doc_id in enumerate(sparse_ranks):\n        scores[doc_id] = scores.get(doc_id, 0) + 1 / (k + rank + 1)\n    return sorted(scores.items(), key=lambda x: x[1], reverse=True)",
      "condition": "",
      "sources": []
    },
    {
      "action": "Configure the vector database to use built-in score normalization. For Qdrant, set `quantization_config=models.ScalarQuantization(scalar=models.ScalarQuantizationConfig(type=models.ScalarType.INT8))` which normalizes scores internally.",
      "success_rate": 0.82,
      "how": "Configure the vector database to use built-in score normalization. For Qdrant, set `quantization_config=models.ScalarQuantization(scalar=models.ScalarQuantizationConfig(type=models.ScalarType.INT8))` which normalizes scores internally.",
      "condition": "",
      "sources": []
    }
  ],
  "workarounds_zh": [
    "在融合前使用最小-最大缩放将两组分数归一化到 [0,1]。示例：\n\ndef normalize_scores(scores):\n    min_s, max_s = min(scores), max(scores)\n    if max_s == min_s:\n        return [0.0] * len(scores)\n    return [(s - min_s) / (max_s - min_s) for s in scores]\n\ndense_scores = normalize_scores(dense_scores)\nsparse_scores = normalize_scores(sparse_scores)\nhybrid_scores = [alpha * d + (1-alpha) * s for d, s in zip(dense_scores, sparse_scores)]",
    "使用基于排名的融合（RRF）而不是基于分数的融合。RRF 直接合并排名，完全避免尺度问题。示例：\n\ndef reciprocal_rank_fusion(dense_ranks, sparse_ranks, k=60):\n    scores = {}\n    for rank, doc_id in enumerate(dense_ranks):\n        scores[doc_id] = 1 / (k + rank + 1)\n    for rank, doc_id in enumerate(sparse_ranks):\n        scores[doc_id] = scores.get(doc_id, 0) + 1 / (k + rank + 1)\n    return sorted(scores.items(), key=lambda x: x[1], reverse=True)",
    "配置向量数据库使用内置分数归一化。对于 Qdrant，设置 `quantization_config=models.ScalarQuantization(scalar=models.ScalarQuantizationConfig(type=models.ScalarType.INT8))`，这会在内部归一化分数。"
  ],
  "transition_graph": {
    "leads_to": [],
    "preceded_by": [],
    "frequently_confused_with": []
  },
  "official_doc_url": "https://qdrant.tech/documentation/concepts/hybrid-queries/#score-normalization",
  "official_doc_section": null,
  "error_code": null,
  "verification_tier": "ai_generated",
  "confidence": 0.87,
  "fix_success_rate": 0.8,
  "resolvable": "true",
  "first_seen": "2024-05-22",
  "last_confirmed": "2024-06-01",
  "last_updated": "2024-06-01",
  "evidence_count": 1,
  "tags": [],
  "locale": "en",
  "aliases": []
}