La recherche vectorielle « dense » est excellente pour le sens — mais elle a un angle mort : les termes exacts. Un code produit (SKU-4471), un nom propre, un acronyme rare, un numéro de version : l’embedding les noie. La recherche « sparse » (lexicale, BM25), elle, les trouve au mot près, mais ignore les synonymes et le sens.

Le RAG hybride combine les deux et prend le meilleur de chacune. Sur AWS, on le bâtit avec OpenSearch Serverless (qui fait kNN et BM25) et Bedrock (modèle, embeddings, reranking). C’est notre architecture de récupération de référence.

Fig.01 · RAG hybride
RRF requête dense kNN · vecteurs sparse BM25 · mots-clés rerank Bedrock LLM
La requête lance en parallèle une recherche dense (kNN sur les vecteurs) et sparse (BM25 sur le texte). Les deux listes sont fusionnées (RRF), reclassées, puis le top-k alimente le LLM.

Étape 1 — Indexer pour les deux recherches

Sur OpenSearch, un même index porte à la fois le texte (pour BM25) et son vecteur (pour le kNN). On définit donc un mapping avec un champ text et un champ knn_vector.

from opensearchpy import OpenSearch, RequestsHttpConnection
from requests_aws4auth import AWS4Auth
import boto3

region = "eu-west-1"
creds = boto3.Session().get_credentials()
auth = AWS4Auth(creds.access_key, creds.secret_key, region, "aoss",
                session_token=creds.token)   # 'aoss' = OpenSearch Serverless

client = OpenSearch(hosts=[{"host": ENDPOINT, "port": 443}], http_auth=auth,
                    use_ssl=True, connection_class=RequestsHttpConnection)

client.indices.create("kb", body={
    "settings": {"index": {"knn": True}},
    "mappings": {"properties": {
        "text": {"type": "text"},                       # pour BM25 (sparse)
        "embedding": {"type": "knn_vector", "dimension": 1024,
                      "method": {"name": "hnsw", "engine": "faiss"}},  # pour kNN (dense)
        "source": {"type": "keyword"},
    }},
})

Les embeddings viennent de Bedrock (Titan Text Embeddings v2, 1024 dimensions ici).

from langchain_aws import BedrockEmbeddings
embeddings = BedrockEmbeddings(model_id="amazon.titan-embed-text-v2:0", region_name=region)

def indexer(fragments: list[dict]):
    for f in fragments:
        client.index(index="kb", body={
            "text": f["text"],
            "embedding": embeddings.embed_query(f["text"]),
            "source": f["source"],
        })

Étape 2 — La requête hybride

OpenSearch exécute les deux recherches dans une seule requête grâce à la clause hybrid, et fusionne les scores via une search pipeline configurée pour la normalisation et la combinaison (typiquement RRF — Reciprocal Rank Fusion).

def chercher_hybride(question: str, k: int = 20) -> list[dict]:
    vecteur = embeddings.embed_query(question)
    corps = {
        "size": k,
        "query": {"hybrid": {"queries": [
            {"match": {"text": {"query": question}}},          # branche SPARSE (BM25)
            {"knn": {"embedding": {"vector": vecteur, "k": k}}},  # branche DENSE (kNN)
        ]}},
    }
    # search_pipeline = normalisation min-max + combinaison (RRF / pondérée)
    res = client.search(index="kb", body=corps, params={"search_pipeline": "hybride-rrf"})
    return [{"text": h["_source"]["text"], "source": h["_source"]["source"],
             "score": h["_score"]} for h in res["hits"]["hits"]]

Étape 3 — Reranker le résultat fusionné

La fusion donne un bon top-20, mais l’ordre reste imparfait. Un reranker — un modèle qui note la pertinence de chaque document vis-à-vis de la requête — réordonne finement et permet de ne garder que le top-5 le plus pertinent. Bedrock fournit des modèles de rerank (Cohere Rerank, Amazon Rerank) via une API dédiée.

from langchain_aws import BedrockRerank

reranker = BedrockRerank(model_id="amazon.rerank-v1:0", region_name=region, top_n=5)

def recuperer(question: str) -> list[dict]:
    candidats = chercher_hybride(question, k=20)     # rappel large
    docs = [c["text"] for c in candidats]
    classes = reranker.rerank(documents=docs, query=question)   # précision fine
    return [candidats[c["index"]] for c in classes]

Le schéma rappel large (hybride) → précision fine (rerank) est la recette qui maximise la qualité : on ratisse large, puis on resserre.

Étape 4 — Brancher la récupération à un agent LangGraph

On expose la récupération hybride comme un outil, et l’agent décide quand l’appeler — c’est le RAG agentique du cas pratique, mais avec une récupération AWS de qualité production.

from langchain_core.tools import tool
from langchain_aws import ChatBedrockConverse
from langgraph.prebuilt import create_react_agent

@tool
def base_de_connaissances(requete: str) -> str:
    """Recherche hybride (sémantique + mots-clés) dans la base de connaissances."""
    docs = recuperer(requete)
    if not docs:
        return "AUCUN_RESULTAT"
    return "\n\n---\n\n".join(f"[{d['source']}]\n{d['text']}" for d in docs)

modele = ChatBedrockConverse(model="eu.anthropic.claude-sonnet-4-5-20250929-v1:0",
                             region_name=region)
agent = create_react_agent(modele, [base_de_connaissances])

Et Knowledge Bases dans tout ça ?

Bedrock Knowledge Bases propose l’hybride en managé : il gère ingestion, index OpenSearch, recherche hybride et reranking pour vous, derrière une seule API. On l’utilise quand on veut aller vite et déléguer l’infra ; on garde l’approche explicite ci-dessus quand on veut contrôler finement la fusion, le chunking ou le reranking. Les deux options sont détaillées dans le deep-dive Knowledge Bases.

Évaluer l’hybride contre le dense

Ne croyez pas l’hybride sur parole : mesurez le gain. Avec le dataset d’évaluation, comparez dense seul vs hybride vs hybride+rerank sur vos vraies questions — surtout celles à termes exacts (codes, références). C’est là que l’hybride brille, et la mesure le prouve.

Bonnes pratiques & pièges

Bonnes pratiques
  • Indexez texte ET vecteur dans le même index OpenSearch : un seul appel hybride.
  • Réglez la fusion dans la search pipeline (RRF par défaut), pas dans le code applicatif.
  • Appliquez le schéma rappel large (hybride, k=20) → précision fine (rerank, top-5).
  • Exposez la récupération comme un outil : l'agent décide quand chercher.
  • Mesurez l'hybride vs dense sur un dataset, surtout les requêtes à termes exacts.
  • Soignez le chunking en amont : l'hybride n'efface pas un index mal découpé.
Pièges à éviter
  • Se contenter du dense : les codes, références et acronymes passent à la trappe.
  • Sparse seul : on rate les synonymes et la reformulation sémantique.
  • Fusionner des scores non normalisés : dense et BM25 ne sont pas à la même échelle.
  • Sauter le reranking : le top fusionné est bon mais rarement optimal.
  • Renvoyer 20 documents bruts au LLM : explosion de tokens — rerank puis top-k.

Ce qu’il faut retenir

  • Le RAG hybride combine dense (sens) et sparse (termes exacts) — chacun couvre l’angle mort de l’autre.
  • Sur AWS, OpenSearch Serverless fait les deux dans un même index ; la fusion (RRF) se règle dans une search pipeline.
  • Le reranking Bedrock transforme un bon rappel en précision : rappel large → précision fine.
  • On expose la récupération comme outil d’un agent LangGraph, modèle Bedrock à la clé.
  • Knowledge Bases offre le même hybride en managé ; on choisit selon le besoin de contrôle — et on mesure toujours le gain.

La récupération est résolue. Voyons maintenant l’option managée de bout en bout : Bedrock Knowledge Bases & Agents face à LangGraph.

#aws#rag#opensearch#bedrock#reranking