Dans un article précédent (30/1/26), nous avons comparé plusieurs métriques de similarité : cosinus, produit scalaire (dot product), euclidienne. Mais comment ces méthodes s’intègrent-elles dans une application réelle ?
Cet article vous guide dans la création d’un mini-moteur de recherche sémantique :
- indexation de documents vectorisés dans QDrant
- interrogation via ASP.NET Core
- choix dynamique de la métrique utilisée
🧰 Prérequis
- QDrant installé localement (Docker ou natif)
- .NET 9 ou 8
- Un modèle d'embedding (OpenAI, HuggingFace ou autre)
- Bibliothèque Qdrant.Client (ou appel HTTP simple)
dotnet add package Qdrant.Client
🧠 Structure de l’app
L'application ASP.NET expose un endpoint HTTP qui reçoit une requête utilisateur (texte libre), calcule son embedding, puis interroge QDrant avec la métrique choisie.
POST /search
{
"query": "Quels sont les bienfaits du magnésium ?",
"metric": "cosine"
}
🏗️ 1. Création du contrôleur SemanticSearchController
[ApiController]
[Route("[controller]")]
public class SemanticSearchController : ControllerBase
{
private readonly QdrantClient _qdrant;
public SemanticSearchController(QdrantClient qdrant)
{
_qdrant = qdrant;
}
[HttpPost("search")]
public async Task<IActionResult> Search([FromBody] SearchQuery query)
{
var embedding = await EmbeddingHelper.GetEmbedding(query.Query); // via OpenAI ou autre
var results = await _qdrant.SearchAsync(new SearchPointsRequest
{
CollectionName = "articles",
Vector = embedding,
Top = 5,
Params = new SearchParams { HnswEf = 128 },
SearchParams = new Distance(query.Metric switch
{
"cosine" => Distance.Cosine,
"dot" => Distance.Dot,
"euclid" => Distance.Euclid,
_ => Distance.Cosine
})
});
return Ok(results);
}
}
public class SearchQuery
{
public string Query { get; set; }
public string Metric { get; set; } = "cosine";
}
📐 2. Paramétrer QDrant avec la bonne distance
Chaque collection dans QDrant est créée avec une métrique fixe.
❗ Cela signifie qu’il faut créer plusieurs collections si vous voulez pouvoir comparer différents modes :
{
"name": "articles_cosine",
"vectors": {
"size": 1536,
"distance": "Cosine"
}
}
Vous pouvez gérer cette distinction via un champ CollectionName en fonction de la métrique sélectionnée.
🔁 3. Exemple de méthode d’embedding avec Azure OpenAI
public static class EmbeddingHelper
{
private static readonly HttpClient client = new();
public static async Task<float[]> GetEmbedding(string text)
{
var payload = new
{
input = text,
model = "text-embedding-3-small"
};
var content = new StringContent(JsonSerializer.Serialize(payload), Encoding.UTF8, "application/json");
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", "VOTRE_CLE");
var response = await client.PostAsync("https://votre-endpoint.openai.azure.com/openai/deployments/embedding-model/embeddings?api-version=2024-02-15-preview", content);
var json = await response.Content.ReadAsStringAsync();
var doc = JsonDocument.Parse(json);
var array = doc.RootElement.GetProperty("data")[0].GetProperty("embedding").EnumerateArray();
return array.Select(e => (float)e.GetDouble()).ToArray();
}
}
🎯 Pour aller plus loin
- Ajouter une couche de score pour pondérer les réponses selon un seuil
- Intégrer la mémoire conversationnelle dans la requête
- Stocker les résultats pour audit ou amélioration continue
- Mettre en place un mécanisme de feedback utilisateur (utile pour l’apprentissage par renforcement)
✅ Conclusion
Ce mini moteur de recherche vectoriel vous donne un socle pour construire :
- un assistant documentaire intelligent
- un bot métier basé sur la similarité sémantique
- une interface interactive pour les contenus indexés
QDrant, combiné à .NET et OpenAI, vous offre une solution self-hosted, performante et contrôlable, sans dépendance à Azure AI Search.
Stay Tuned !