---
title: "RAG sur SQL Server 2025 en .NET : architecture"
description: "Brancher un RAG sur une application .NET et SQL Server sans base vectorielle à part : le type VECTOR, un découpage qui suit les documents, les habilitations appliquées dans la requête, un agent Agent Framework. Code complet et testé sur GitHub."
url: "https://gilabs.fr/fr/guides/rag-sql-server-2025/"
lang: "fr"
author: "Yann Gilliot"
published: "2026-09-30"
tags: ["RAG", "SQL Server", ".NET", "Agent Framework", "Architecture"]
---
# RAG sur SQL Server 2025 : architecture de référence pour une application .NET existante

Par [Yann Gilliot](https://gilabs.fr/fr/a-propos/) · publié le 2026-09-30

Brancher un RAG sur une application .NET et SQL Server sans base vectorielle à part : le type VECTOR, un découpage qui suit les documents, les habilitations appliquées dans la requête, un agent Agent Framework. Code complet et testé sur GitHub.

## L'essentiel

- SQL Server 2025 stocke les embeddings dans un type VECTOR et calcule les distances avec VECTOR_DISTANCE, deux fonctionnalités disponibles en version finale. La base de l'application peut servir de base vectorielle : pas de second système à sécuriser.
- L'index vectoriel DiskANN reste en préversion sur SQL Server 2025 (jusqu'au CU9 du 15 septembre 2026) : table en lecture seule une fois indexée, filtres appliqués après la recherche. La recherche exacte est le bon choix par défaut, recommandée par Microsoft jusqu'à environ 50 000 vecteurs après filtrage.
- Les habilitations s'appliquent dans la requête SQL, sur la table de droits que l'application utilise déjà, avec les rôles tirés de l'identité côté serveur. Un fragment que l'utilisateur n'a pas le droit de lire n'atteint jamais le modèle.
- Le découpage suit la structure du document (sections, paragraphes, tableaux entiers) et chaque fragment porte le chemin de ses titres dans son embedding.

En juillet, j'ai publié trois décisions pour brancher un RAG sur un SI .NET existant : garder les vecteurs à côté des données, découper selon la structure des documents, laisser l'orchestration dans la couche service. Ce guide remplace cet article. Les trois décisions tiennent toujours, mais le texte restait au niveau des principes. Voici l'architecture complète, avec le SQL et le C#.

Deux choses ont changé depuis. SQL Server 2025, sorti le 18 novembre 2025, stocke les embeddings dans un vrai type de colonne. Et Microsoft Agent Framework 1.0, sorti en avril 2026, a remplacé Semantic Kernel pour la couche agent (voir [le guide de migration](https://gilabs.fr/fr/guides/semantic-kernel-agent-framework/)). Le code de ce guide vient d'un [dépôt GitHub public](https://github.com/ygilliot/rag-sql-server-dotnet), compilé et testé contre SQL Server 2025 à chaque modification.

## Où en est SQL Server 2025 sur les vecteurs

La documentation mélange ce qui est disponible en version finale et ce qui est en préversion, et la différence entre SQL Server installé chez vous et Azure SQL compte. État au 30 septembre 2026 :

| Fonction | SQL Server 2025 (jusqu'au CU9, 15/09/2026) | Azure SQL Database, SQL database dans Fabric |
|---|---|---|
| Type `VECTOR`, jusqu'à 1 998 dimensions en float32 | Disponible | Disponible |
| `VECTOR_DISTANCE` (cosinus, euclidienne, produit scalaire) | Disponible | Disponible |
| Transport binaire depuis .NET (`SqlVector<float>`) | Microsoft.Data.SqlClient 6.1 ou plus | Idem |
| Index `CREATE VECTOR INDEX` (DiskANN) et `VECTOR_SEARCH` | Préversion, option `PREVIEW_FEATURES` | Disponible |
| Écriture dans une table indexée, filtres appliqués pendant la recherche | Non : ancien format d'index, table en lecture seule, filtre après coup | Oui (dernier format d'index) |

Retenez la dernière ligne. Sur SQL Server 2025 installé chez vous, l'index vectoriel actuel rend la table en lecture seule une fois créé, et les conditions du `WHERE` s'appliquent après la recherche approchée. Pour un RAG filtré par habilitations, cela veut dire qu'une requête peut renvoyer zéro résultat alors que des documents autorisés existent. La recherche exacte n'a pas ce défaut, et elle suffit dans la grande majorité des cas (voir plus bas).

## L'architecture en un tableau

| Couche | Ce qui existe déjà | Ce que le RAG ajoute |
|---|---|---|
| Données | La table des documents de l'application | Une table de fragments avec une colonne `VECTOR(1536)` |
| Droits | La table ou le mécanisme d'habilitations en place | Rien : la recherche le réutilise |
| Service .NET | L'identité de l'utilisateur, la journalisation, l'audit | Un découpeur, un ingesteur, un moteur de recherche |
| Interface | L'application | Un agent Agent Framework qui répond à partir des fragments autorisés |

Une seule table nouvelle, aucun nouveau système. C'est tout l'intérêt de faire le RAG dans SQL Server quand l'application y vit déjà.

## Décision 1 : les vecteurs restent à côté des données

La table des fragments pointe vers la table des documents existante. Elle ne copie ni le titre, ni les droits :

```sql
CREATE TABLE dbo.DocumentChunks
(
    ChunkId        INT IDENTITY(1, 1) NOT NULL CONSTRAINT PK_DocumentChunks PRIMARY KEY CLUSTERED,
    DocumentId     INT                NOT NULL CONSTRAINT FK_DocumentChunks_Documents
                                               REFERENCES dbo.Documents (DocumentId) ON DELETE CASCADE,
    Ordinal        INT                NOT NULL,
    HeadingPath    NVARCHAR(400)      NOT NULL,
    Content        NVARCHAR(MAX)      NOT NULL,
    Embedding      VECTOR(1536)       NOT NULL,
    EmbeddingModel NVARCHAR(100)      NOT NULL
);
```

Trois détails qui évitent des ennuis plus tard. La clé primaire est un `INT` en index cluster, condition exigée si vous ajoutez un jour un index vectoriel. La suppression en cascade retire les fragments quand le document disparaît : pas de fragment orphelin qui continue de répondre. Et la colonne `EmbeddingModel` enregistre le modèle qui a produit chaque vecteur. Deux modèles d'embedding ne sont pas comparables entre eux : le jour où vous changez de modèle, cette colonne dit ce qu'il reste à recalculer, et la recherche filtre dessus pour ne jamais mélanger les deux.

## Décision 2 : le découpage suit la structure du document

Couper tous les 500 tokens est simple, et coupe une clause, un tableau ou une étape de procédure en deux. Le modèle reçoit la moitié d'une règle et répond à partir de cette moitié. Les documents métier portent déjà leur structure ; le découpeur du dépôt la respecte :

- un fragment par section, délimitée par les titres ;
- une section trop longue est coupée entre deux paragraphes, jamais au milieu d'un ;
- un tableau ou un bloc de code reste entier, même s'il dépasse la taille cible ;
- chaque fragment garde le chemin de ses titres, par exemple « Politique de frais > Déplacements > Hôtels ».

Ce chemin est ajouté au texte envoyé au modèle d'embedding. Un fragment qui dit seulement « plafond : 120 euros par nuit » ne dit pas de quoi il parle ; avec son chemin, la question « combien pour un hôtel ? » le retrouve :

```csharp
public sealed record DocumentChunk(int Ordinal, string HeadingPath, string Content)
{
    // The heading path travels with the text into the embedding.
    public string EmbeddingText => $"{HeadingPath}\n\n{Content}";
}
```

Le découpeur du dépôt travaille sur du Markdown. Pour des PDF ou des fichiers Word, la conversion en amont compte autant que le découpage : c'est le rôle de votre chaîne d'extraction existante, ou de la bibliothèque `Microsoft.Extensions.DataIngestion` de Microsoft, encore en préversion, qui propose aussi un découpage par titres.

## Décision 3 : les habilitations s'appliquent dans la requête

C'est la décision qui compte le plus, et celle que les tutoriels sautent. La recherche filtre sur la table de droits de l'application, dans la même requête que le calcul de distance :

```sql
SELECT TOP (@top)
    c.ChunkId, c.DocumentId, d.Title, d.SourceUri, c.HeadingPath, c.Content,
    VECTOR_DISTANCE('cosine', c.Embedding, @query) AS Distance
FROM dbo.DocumentChunks AS c
INNER JOIN dbo.Documents AS d ON d.DocumentId = c.DocumentId
WHERE c.EmbeddingModel = @model
  AND EXISTS (
      SELECT 1
      FROM dbo.DocumentAccess AS a
      INNER JOIN OPENJSON(@roles) WITH (RoleName NVARCHAR(100) '$') AS r
          ON r.RoleName = a.RoleName
      WHERE a.DocumentId = c.DocumentId)
ORDER BY Distance;
```

Côté .NET, le vecteur de la question part en binaire, sans passer par du JSON :

```csharp
// Fail closed: a caller without any role sees nothing, rather than everything.
if (caller.Roles.Count == 0)
{
    return [];
}

var queryVector = await embeddingGenerator.GenerateVectorAsync(question, cancellationToken: cancellationToken);

command.Parameters.Add("@roles", SqlDbType.NVarChar, -1).Value = JsonSerializer.Serialize(caller.Roles);
command.Parameters.Add("@query", SqlDbTypeExtensions.Vector).Value = new SqlVector<float>(queryVector);
```

Deux règles derrière ce code. Les rôles viennent de l'identité côté serveur (jeton, session, claims), jamais d'un paramètre que le modèle ou le client pourrait fournir. Et un utilisateur sans rôle ne voit rien : en cas de doute, la recherche échoue fermée. Dans votre application, la table `DocumentAccess` est remplacée par votre modèle de droits réel, qu'il s'agisse de rôles, de services, d'une sécurité au niveau des lignes ou d'une vue qui les combine. Le principe ne change pas : le filtre vit là où vivent déjà les droits.

## Recherche exacte ou index vectoriel ?

La recherche exacte calcule la distance avec chaque fragment candidat. Microsoft la recommande tant que la recherche porte sur moins de 50 000 vecteurs environ, comptés **après** les conditions du `WHERE`. Avec un filtre d'habilitations, ce seuil est rarement atteint : un utilisateur a le droit de lire une fraction des documents, pas tous.

| Critère | Recherche exacte (`VECTOR_DISTANCE`) | Index DiskANN sur SQL Server 2025 | Index DiskANN sur Azure SQL |
|---|---|---|---|
| Statut | Disponible | Préversion | Disponible |
| Résultats | Exacts | Approchés | Approchés |
| Filtre d'habilitations | Appliqué avant, résultats complets | Appliqué après, résultats manquants possibles | Appliqué pendant la recherche |
| Écritures dans la table | Normales | Table en lecture seule (sauf option `ALLOW_STALE_VECTOR_INDEX`) | Normales |
| Volume adapté | Jusqu'à environ 50 000 vecteurs après filtrage | Grands volumes, données stables | Grands volumes |

Commencez en recherche exacte, mesurez le temps de réponse sur vos volumes réels, et ne passez à l'index que si la mesure l'exige. Le dépôt contient un script séparé pour l'index, avec ces réserves en tête.

## Brancher l'agent

La couche agent utilise Microsoft Agent Framework. La recherche se branche comme fournisseur de contexte : `TextSearchProvider` l'exécute avant chaque appel au modèle, avec l'utilisateur capturé côté serveur :

```csharp
public static AIAgent Create(IChatClient chatClient, ChunkRetriever retriever, ICallerContext caller) =>
    chatClient.AsAIAgent(new ChatClientAgentOptions
    {
        ChatOptions = new() { Instructions = Instructions },
        AIContextProviders =
        [
            new TextSearchProvider(
                (query, cancellationToken) => SearchAsync(retriever, caller, query, cancellationToken),
                new TextSearchProviderOptions
                {
                    SearchTime = TextSearchProviderOptions.TextSearchBehavior.BeforeAIInvoke,
                }),
        ],
    });
```

L'agent se crée à chaque requête, avec l'utilisateur de la requête : c'est un objet léger autour du client de chat partagé. Si vous choisissez plutôt le mode où le modèle appelle lui-même un outil de recherche, gardez les rôles hors des paramètres de l'outil. Sinon, c'est le modèle qui décide de ce que l'utilisateur a le droit de lire.

## Ce que les tests vérifient

Les tests du dépôt tournent contre un vrai SQL Server 2025 à chaque modification, avec un générateur d'embeddings déterministe qui ne demande aucune clé d'API. Ils vérifient :

- qu'un salarié retrouve le plafond d'hôtel dans la politique de frais, avec sa source ;
- qu'un salarié ne récupère jamais un fragment du document RH sur les salaires, même avec une question qui en reprend les mots ;
- qu'un utilisateur avec plusieurs rôles voit l'union de ses droits, et un utilisateur sans rôle, rien ;
- qu'une nouvelle ingestion d'un document remplace ses fragments au lieu de les dupliquer ;
- et surtout, en faisant tourner l'agent complet devant un faux modèle qui enregistre tout ce qu'il reçoit, que le texte d'un document interdit n'arrive jamais dans le prompt.

Ce dernier test est celui que je recommande de reprendre chez vous. Vérifier ce que renvoie la requête ne suffit pas : ce qui compte, c'est ce qui atteint le modèle.

## Checklist avant la production

1. **Identifier la source des droits.** Où vit la règle « qui peut lire quel document » aujourd'hui ? C'est elle que la recherche doit interroger, pas une copie.
2. **Choisir le modèle d'embedding et sa dimension.** La colonne `VECTOR(n)` en dépend. Pour une profession réglementée, un modèle hébergé dans l'Union européenne, voire sur site.
3. **Prévoir le changement de modèle.** Colonne `EmbeddingModel`, procédure de recalcul, et filtre sur le modèle dans la recherche.
4. **Relire vingt fragments.** Sur vos vrais documents, avant tout réglage : un découpage raté se voit à l'œil nu.
5. **Constituer un jeu de questions de référence.** Une trentaine de questions réelles, avec les documents attendus et ceux qui ne doivent jamais sortir pour chaque profil.
6. **Échouer fermé.** Pas de rôle, pas de résultat. Une erreur de droits, pas de résultat.
7. **Traiter les embeddings comme les documents.** Un vecteur dérive du texte : même niveau de confidentialité, même durée de conservation, même suppression.
8. **Mesurer avant d'indexer.** Temps de réponse et nombre de fragments candidats par profil ; l'index vectoriel seulement si ces mesures le justifient.

## Pour aller plus loin

Le [dépôt GitHub](https://github.com/ygilliot/rag-sql-server-dotnet) contient le schéma, le découpeur, l'ingestion, la recherche, l'agent, les tests et un exemple qui tourne sans clé d'API. Il vérifie aussi, à chaque modification, que le code publié dans le guide [Semantic Kernel ou Microsoft Agent Framework](https://gilabs.fr/fr/guides/semantic-kernel-agent-framework/) compile toujours. Pour l'écart entre une démonstration qui marche et un système qui tient, voir [ce qui tient en production](https://gilabs.fr/fr/blog/ce-qui-tient-en-production/). Et si vous voulez rendre vos documents métier interrogeables depuis vos applications existantes, c'est le cœur de l'[intégration IA dans les applications .NET](https://gilabs.fr/fr/offres/integration-ia-dotnet/).

## Sources et références

- [Dépôt GitHub du guide : rag-sql-server-dotnet (code complet et tests)](https://github.com/ygilliot/rag-sql-server-dotnet), GitHub
- [SQL Server 2025 Embraces Vectors (sortie de SQL Server 2025, 18 novembre 2025)](https://devblogs.microsoft.com/azure-sql/sql-server-2025-embraces-vectors-setting-the-foundation-for-empowering-your-data-with-ai/), Microsoft
- [Vector data type](https://learn.microsoft.com/en-us/sql/t-sql/data-types/vector-data-type), Microsoft Learn
- [Vector search and vector indexes in the SQL Database Engine](https://learn.microsoft.com/en-us/sql/sql-server/ai/vectors), Microsoft Learn
- [CREATE VECTOR INDEX (Transact-SQL)](https://learn.microsoft.com/en-us/sql/t-sql/statements/create-vector-index-transact-sql), Microsoft Learn
- [VECTOR_SEARCH (Transact-SQL)](https://learn.microsoft.com/en-us/sql/t-sql/functions/vector-search-transact-sql), Microsoft Learn
- [SQL Server 2025 build versions (CU9 du 15 septembre 2026)](https://learn.microsoft.com/en-us/troubleshoot/sql/releases/sqlserver-2025/build-versions), Microsoft Learn
- [RAG avec Agent Framework (TextSearchProvider)](https://learn.microsoft.com/en-us/agent-framework/agents/rag), Microsoft Learn

Thèmes : RAG, SQL Server, .NET, Agent Framework, Architecture

## Aller plus loin

Vos documents métier sont-ils interrogeables depuis vos applications, sans ouvrir un second système de droits ?

Pour les ETI et les éditeurs dont le SI vit en .NET et SQL Server : l'IA développée dans votre applicatif (extraction documentaire, agents, classification) avec vos règles métier, votre architecture, votre code. Pas un outil de plus à côté du SI : une capacité nouvelle dedans. Je travaille dans votre repo, avec vos développeurs, aux standards de votre équipe.

[Intégration IA .NET](https://gilabs.fr/fr/offres/integration-ia-dotnet/)

**Réserver un échange de 30 minutes** : [Calendly](https://calendly.com/yann-gilabs/30min)
