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.
É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
- 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é.
- 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.