Accueil / Articles / Notes pratiques : PostgreSQL + pgvector et SQL Server 2025 en tant que stockages vectoriels

Notes pratiques : PostgreSQL + pgvector et SQL Server 2025 en tant que stockages vectoriels

Guide opérationnel des notes pratiques : PostgreSQL + pgvector et SQL Server 2025 en tant que stockages vectoriels : contrats, vérifications et emplacements de code prêts à l’emploi pour les équipes qui implémentent ce modèle.

4064 mots

Les notes suivantes reconstituent une approche pratique basée sur « PostgreSQL + pgvector et SQL Server 2025 en tant que stockages vectoriels pour RAG — Un guide pratique ». L’accent est mis sur les contrats, les vérifications et les placeholders de code à insérer directement, plutôt que sur une présentation motivante.

Le paysage des bases de données vectorielles en 2025

Lors de l’étape concernant le paysage des bases de données vectorielles, notez d’abord les contrats : entrées requises, signal de succès et ce qui se passe en cas d’échec partiel. Cette liste de contrôle permet de rester honnête lors des modifications ultérieures du code. Considérez cette étape comme un contrat entre les entrées et les sorties validées. Donnez des noms aux artefacts, définez des vérifications de succès et refusez les terminaisons partielles silencieuses. Mesurez le taux de rappel sur un ensemble de questions fixe avant d’ajuster les prompts. Changer fréquemment les prompts ne résout que rarement un système de récupération insuffisant.

-- Find the most relevant chunks, but only from documents
-- belonging to enterprise-tier customers — a single SQL query
SELECT c.content, 1 - (c.embedding <=> query_vec) AS score
FROM rag_chunks c
JOIN documents d ON d.filename = c.source
JOIN customers cu ON cu.id = d.customer_id
WHERE cu.tier = 'enterprise'
ORDER BY score DESC
LIMIT 5;

L’ensemble du stack

Lorsque vous travaillez sur l’étape The Full Stack, notez d’abord le contrat : les entrées requises, le signal de succès et ce qui se passe en cas d’échec partiel. Cette liste de contrôle permet de rester honnête lors des modifications ultérieures du code. Enregistrez les temps d’exécution ainsi que le coût des tokens ou des requêtes à côté des résultats fonctionnels. Une visibilité précoce des coûts évite les factures inattendues lorsque le processus passe de la démonstration aux environnements partagés. Mesurez le taux de rappel sur un ensemble fixe de questions avant d’ajuster les prompts. Un changement fréquent des prompts ne résout que rarement un système de récupération insuffisant.

Partie 1 — PostgreSQL 18 + pgvector

Lorsque vous travaillez sur l’étape Partie 1 PostgreSQL 18, notez d’abord les exigences : entrées requises, signal de succès, et ce qui se passe en cas d’échec partiel. Cette liste de contrôle permet de rester honnête lors des modifications ultérieures du code. Gardez la configuration en dehors du code de l’application. Les fichiers d’environnement, les bases de données secrètes et les indicateurs fonctionnels doivent être regroupés en un seul endroit que les administrateurs peuvent auditer sans devoir lire l’ensemble du système. Mesurez le taux de rappel sur un ensemble fixe de questions avant d’ajuster les prompts. Un changement fréquent des prompts ne résout que rarement un système de récupération insuffisant.

Installation de pgvector sous Windows

Lors de l’étape d’installation de pgvector sous Windows, notez d’abord les exigences : entrées requises, signal de succès et conséquences en cas d’échec partiel. Cette liste de contrôle permet de rester honnête lors des modifications ultérieures du code. Documentez à la fois le parcours normal et les scénarios de récupération. Les tentatives répétées, les contrôles humains et la gestion des messages non traités font partie intégrante du produit, et non d’améliorations apportées ultérieurement. Évaluez le taux de rappel sur un ensemble fixe de questions avant d’ajuster les prompts. Un changement fréquent des prompts ne résout que rarement un système de récupération insuffisant.

set "PGROOT=C:\Program Files\PostgreSQL\18"
cd %TEMP%
git clone --branch v0.8.0 https://github.com/pgvector/pgvector.git
cd pgvector
nmake /F Makefile.win
nmake /F Makefile.win install
docker run -d -p 5432:5432 -e POSTGRES_PASSWORD=postgres --name pgvector pgvector/pgvector:pg18
conda install -c conda-forge pgvector

Création de tables dans pgAdmin

Lors de l’étape de création de tables dans pgAdmin, notez d’abord les spécifications : les entrées requises, le signal de succès et ce qui se passe en cas d’échec partiel. Cette liste de contrôle permet de rester honnête lors des modifications ultérieures du code. Préférez des unités petites et testables plutôt que des scripts complexes. Lorsqu’une étape échoue, l’échec doit indiquer une seule responsabilité et non un processus embrouillé. Évaluez le taux de rappel sur un ensemble fixe de questions avant d’ajuster les prompts. Un changement fréquent des prompts résout rarement un système de récupération insuffisant. Lors de l’étape de création de tables dans pgAdmin, notez d’abord les spécifications : les entrées requises, le signal de succès et ce qui se passe en cas d’échec partiel. Cette liste de contrôle permet de rester honnête lors des modifications ultérieures du code. Enregistrez les temps d’exécution ainsi que le coût en tokens ou en requêtes à côté des résultats fonctionnels. Une visibilité précoce des coûts évite les factures inattendues lorsque le système passe de l’environnement de démonstration à des environnements partagés.

-- Enable the pgvector extension
CREATE EXTENSION IF NOT EXISTS vector;

-- Main chunks table with VECTOR(768) column
CREATE TABLE IF NOT EXISTS rag_chunks (
    id           UUID        PRIMARY KEY DEFAULT gen_random_uuid(),
    source       TEXT        NOT NULL,
    chunk_index  INTEGER     NOT NULL,
    content      TEXT        NOT NULL,
    file_hash    TEXT,
    ingested_at  TIMESTAMPTZ DEFAULT NOW(),
    embedding    vector(768)  -- pgvector native type
);

-- HNSW index for approximate cosine similarity search
CREATE INDEX IF NOT EXISTS idx_rag_chunks_embedding
    ON rag_chunks USING hnsw (embedding vector_cosine_ops);

-- Source filter index
CREATE INDEX IF NOT EXISTS idx_rag_chunks_source
    ON rag_chunks (source);

-- Staleness registry
CREATE TABLE IF NOT EXISTS rag_staleness (
    doc_name    TEXT        PRIMARY KEY,
    file_hash   TEXT        NOT NULL,
    chunk_count INTEGER,
    ingested_at TIMESTAMPTZ DEFAULT NOW(),
    version     INTEGER     DEFAULT 1
);

-- CDC chunk registry
CREATE TABLE IF NOT EXISTS rag_chunk_registry (
    doc_name    TEXT NOT NULL,
    chunk_hash  TEXT NOT NULL,
    chunk_id    TEXT NOT NULL,
    PRIMARY KEY (doc_name, chunk_hash)
);

-- Conversation sessions
CREATE TABLE IF NOT EXISTS rag_sessions (
    session_id  TEXT        PRIMARY KEY,
    created_at  TIMESTAMPTZ DEFAULT NOW(),
    updated_at  TIMESTAMPTZ DEFAULT NOW(),
    model       TEXT,
    embed_model TEXT,
    turn_count  INTEGER     DEFAULT 0
);

-- Conversation turns
CREATE TABLE IF NOT EXISTS rag_turns (
    id          SERIAL      PRIMARY KEY,
    session_id  TEXT        REFERENCES rag_sessions(session_id) ON DELETE CASCADE,
    role        TEXT        NOT NULL CHECK (role IN ('user','assistant')),
    content     TEXT        NOT NULL,
    sources     TEXT[],
    created_at  TIMESTAMPTZ DEFAULT NOW()
);

CREATE INDEX IF NOT EXISTS idx_rag_turns_session
    ON rag_turns (session_id, created_at);

Dépendances Python

L’étape des dépendances Python fonctionne le mieux lorsqu’elle est considérée comme une surface mesurable. Capturez un exemple réussi, un cas d’échec et la note de réversion avant d’élargir le périmètre. Conservez les configurations en dehors du code de l’application. Les fichiers d’environnement, les bases de données secrètes et les indicateurs fonctionnels doivent être regroupés en un seul endroit que les opérateurs peuvent auditer sans devoir lire l’ensemble du système. Fixez l’interpréteur ainsi que le fichier de verrouillage des dépendances avant d’aborder les boucles. Les différences entre l’ordinateur portable et l’environnement CI sont la cause la plus fréquente de dysfonctionnements silencieux dans les démos API.

uv add google-genai pypdf pgvector psycopg2-binary python-dotenv huggingface_hub

Mise en place de la connexion (Cellule 2)

La phase 2 de l’installation de la connexion fonctionne le mieux lorsqu’elle est considérée comme une surface mesurable. Capturez un transcript parfait, un cas d’échec et la note de réversion avant d’élargir le périmètre. Documentez ensemble le parcours optimal et le parcours de récupération. Les tentatives répétées, les contrôles humains et le traitement des messages non livrés font partie intégrante du produit, et non d’une mise en forme ultérieure. Séparez la politique de segmentation de la politique de récupération : modifier l’une ne doit pas obliger à réécrire l’autre lorsque les métriques de qualité évoluent.

import psycopg2
from pgvector.psycopg2 import register_vector

PG_HOST     = "localhost"
PG_PORT     = 5432
PG_DB       = "postgres"
PG_USER     = "postgres"
PG_PASSWORD = os.environ.get("PG_PASSWORD", "postgres")

def get_pg_conn():
    """Returns a fresh PostgreSQL connection with pgvector registered."""
    conn = psycopg2.connect(
        host=PG_HOST, port=PG_PORT,
        dbname=PG_DB, user=PG_USER, password=PG_PASSWORD
    )
    register_vector(conn)   # tells psycopg2 how to handle vector type
    return conn

Stockage des embeddings (Cellule 6)

La phase 6 de la cellule de stockage des embeddings fonctionne le mieux lorsqu’elle est considérée comme une surface mesurable. Capturez un transcript idéal, un cas d’échec et la note de réversion avant d’élargir le périmètre. Préférez des unités petites et testables plutôt que des scripts complexes. Lorsqu’une étape échoue, l’échec doit pointer vers une seule responsabilité et non vers un processus embrouillé. Séparez la politique de segmentation de la politique de récupération. Modifier l’une ne doit pas obliger à réécrire l’autre lorsque les métriques de qualité changent.

import numpy as np

def store_in_postgres(chunks, embeddings, doc_name) -> int:
    conn = get_pg_conn()
    cur  = conn.cursor()
    for i, (chunk, emb) in enumerate(zip(chunks, embeddings)):
        cur.execute("""
            INSERT INTO rag_chunks (source, chunk_index, content, embedding)
            VALUES (%s, %s, %s, %s)
        """, (doc_name, i, chunk, np.array(emb)))  # np.array → pgvector handles serialization
    conn.commit()
    conn.close()
    return len(chunks)

La phase 6 de la cellule de stockage des embeddings fonctionne le mieux lorsqu’elle est considérée comme une surface mesurable. Capturez un transcript idéal, un cas d’échec et la note de réversion avant d’élargir le périmètre. Enregistrez les temps d’exécution ainsi que le coût en tokens ou requêtes à côté des résultats fonctionnels. Une visibilité précoce des coûts évite des factures inattendues lorsque le processus passe de l’environnement de démonstration à des environnements partagés.

Récupération par similarité cosinus (Cellule 6, suite)

Pour l’étape de récupération par similarité cosinus, définissez les entrées, le responsable de cette étape ainsi que les critères d’arrêt avant de modifier le code. Les opérateurs doivent pouvoir relancer l’étape à partir d’un point de contrôle connu sans deviner l’état caché. Conservez la configuration en dehors du code de l’application. Les fichiers d’environnement, les bases de données secrètes et les flags fonctionnels doivent être regroupés en un seul endroit que les opérateurs peuvent auditer sans avoir à lire l’ensemble du système. Citez les passages qui servent de base à la réponse. Sans citations, les opérateurs ne peuvent pas distinguer une hallucination d’un manque d’indexation.

def retrieve_context(query: str) -> list[dict]:
    query_embedding = embed_query(query)
    conn = get_pg_conn()
    cur  = conn.cursor()

    cur.execute("""
        SELECT
            content,
            source,
            chunk_index,
            ingested_at,
            1 - (embedding <=> %s) AS cosine_score  -- <=> is cosine distance
        FROM rag_chunks
        ORDER BY embedding <=> %s                    -- sort ascending (smallest distance first)
        LIMIT %s
    """, (np.array(query_embedding), np.array(query_embedding), TOP_K))
    rows = cur.fetchall()
    conn.close()
    return [{"text": r[0], "source": r[1], "chunk_index": r[2],
             "ingested_at": str(r[3]) if r[3] else "",
             "score": round(float(r[4]), 4)} for r in rows]

CDC avec pgvector — Schéma Upsert

Pour CDC avec l’étape Upsert de pgvector, définissez les entrées, le responsable de l’étape et les critères de sortie avant de modifier le code. Les opérateurs doivent pouvoir relancer l’étape à partir d’un point de contrôle connu sans deviner l’état caché. Documentez conjointement le parcours normal et le parcours de récupération. Les tentatives répétées, les contrôles humains et la gestion des messages non livrés font partie intégrante du produit, et non d’une mise en forme ultérieure. Citez les passages qui ont réellement servi de base à la réponse. Sans citations, les opérateurs ne peuvent pas distinguer une hallucination d’un manque d’indexation.

cur.execute("""
    INSERT INTO rag_staleness (doc_name, file_hash, chunk_count, version)
    VALUES (%s, %s, %s, 1)
    ON CONFLICT (doc_name) DO UPDATE SET
        file_hash   = EXCLUDED.file_hash,
        chunk_count = EXCLUDED.chunk_count,
        ingested_at = NOW(),
        version     = rag_staleness.version + 1;
""", (doc_name, file_hash, chunk_count))

Partie 2 — SQL Server 2025 (Vector natif)

Pour l’étape SQL Server de la partie 2, définissez les entrées, le responsable de l’étape et les critères d’arrêt avant de modifier le code. Les opérateurs doivent pouvoir relancer l’étape à partir d’un point de contrôle connu sans deviner l’état caché. Préférez des unités petites et testables à des scripts complexes. Lorsqu’une étape échoue, l’échec doit indiquer une seule responsabilité plutôt qu’un pipeline embrouillé. Citez les passages qui ont réellement servi de base à la réponse. Sans citations, les opérateurs ne peuvent pas distinguer une hallucination d’un manque d’indexation.

Pourquoi SQL Server 2025 n’a pas besoin d’extension

Pour l’étape Why SQL Server 2025, définissez les entrées, le responsable de l’étape et les critères de fin avant de modifier le code. Les opérateurs doivent pouvoir relancer l’étape à partir d’un point de contrôle connu sans deviner l’état caché. Considérez cette étape comme un contrat entre les entrées et les sorties validées. Donnez des noms aux artefacts, définites des vérifications de succès et refusez les terminations partielles silencieuses. Citez les passages qui ont réellement servi de base à la réponse. Sans citations, les opérateurs ne peuvent pas distinguer une hallucination d’un manque d’indexation.

Mise en place dans SSMS

Pendant l’étape de configuration dans SSMS, définissez les entrées, le responsable de l’étape et les critères de fin avant de modifier le code. Les opérateurs doivent pouvoir relancer l’étape à partir d’un point de contrôle connu sans deviner l’état caché. Enregistrez les temps d’exécution ainsi que le coût des tokens ou des requêtes à côté des résultats fonctionnels. Une visibilité précoce du coût permet d’éviter des factures inattendues lorsque le processus passe de l’environnement de démonstration à des environnements partagés. Citez les passages qui servent réellement de base à la réponse. Sans citations, les opérateurs ne peuvent pas distinguer une hallucination d’un manque d’indexation.

-- Batch 1: Run this first — must commit before vector objects are recognized
ALTER DATABASE SCOPED CONFIGURATION SET PREVIEW_FEATURES = ON;
-- Batch 2: Create all tables
CREATE TABLE rag_chunks (
    id           INT              IDENTITY(1,1) PRIMARY KEY CLUSTERED,
    source       NVARCHAR(500)    NOT NULL,
    chunk_index  INT              NOT NULL,
    content      NVARCHAR(MAX)    NOT NULL,
    file_hash    NVARCHAR(64),
    ingested_at  DATETIME2        DEFAULT GETUTCDATE(),
    embedding    VECTOR(768)      -- native SQL Server 2025 type
);

CREATE INDEX idx_rag_source ON rag_chunks(source);

CREATE TABLE rag_staleness (
    doc_name    NVARCHAR(500)   PRIMARY KEY,
    file_hash   NVARCHAR(64)    NOT NULL,
    chunk_count INT,
    ingested_at DATETIME2       DEFAULT GETUTCDATE(),
    version     INT             DEFAULT 1
);

CREATE TABLE rag_chunk_registry (
    doc_name    NVARCHAR(500)   NOT NULL,
    chunk_hash  NVARCHAR(64)    NOT NULL,
    chunk_id    NVARCHAR(64)    NOT NULL,
    PRIMARY KEY (doc_name, chunk_hash)
);

CREATE TABLE rag_sessions (
    session_id  NVARCHAR(100)   PRIMARY KEY,
    created_at  DATETIME2       DEFAULT GETUTCDATE(),
    updated_at  DATETIME2       DEFAULT GETUTCDATE(),
    model       NVARCHAR(200),
    embed_model NVARCHAR(200),
    turn_count  INT             DEFAULT 0
);

CREATE TABLE rag_turns (
    id          INT             IDENTITY(1,1) PRIMARY KEY,
    session_id  NVARCHAR(100)   NOT NULL REFERENCES rag_sessions(session_id),
    role        NVARCHAR(20)    NOT NULL CHECK (role IN ('user','assistant')),
    content     NVARCHAR(MAX)   NOT NULL,
    sources     NVARCHAR(MAX),
    created_at  DATETIME2       DEFAULT GETUTCDATE()
);

CREATE INDEX idx_rag_turns_session ON rag_turns(session_id, created_at);
-- Batch 3: Must run AFTER Batch 2 commits
-- Cannot run inside a transaction — this is a known SQL Server 2025 preview constraint
CREATE VECTOR INDEX idx_rag_embedding
    ON rag_chunks(embedding)
    WITH (METRIC = 'COSINE');

Dépendances Python

Pour l’étape des dépendances Python, définitz les entrées, le responsable de l’étape et les critères d’arrêt avant de modifier du code. Les opérateurs doivent pouvoir relancer l’étape à partir d’un point de contrôle connu sans deviner l’état caché. Conservez la configuration en dehors du code de l’application. Les fichiers d’environnement, les stocks de secrets et les flags fonctionnels doivent être regroupés en un seul endroit que les opérateurs peuvent auditer sans devoir lire l’ensemble du graphique. Séparez la construction du client du cycle des messages afin que les fournisseurs puissent être remplacés sans avoir à réécrire la machine à états de la conversation.

uv add google-genai pypdf pyodbc python-dotenv huggingface_hub fpdf2

Mise en place de la connexion (Cellule 2)

Pour l’étape 2 de la mise en place de la connexion, définissez les entrées, le responsable de l’étape et les critères de fin avant de modifier le code. Les opérateurs doivent pouvoir relancer l’étape à partir d’un point de contrôle connu sans deviner l’état caché. Documentez ensemble le parcours normal et le parcours de récupération. Les tentatives répétées, les contrôles humains et la gestion des messages non livrés font partie du produit, et non d’une mise en forme ultérieure. Citez les passages qui ont réellement servi de base à la réponse. Sans citations, les opérateurs ne peuvent pas distinguer une hallucination d’un manque d’indexation.

import pyodbc

SQL_SERVER   = "YOUR_SERVER_NAME"   # from SSMS title bar
SQL_DATABASE = "local_rag"
SQL_CONN_STR = (
    f"DRIVER={{ODBC Driver 17 for SQL Server}};"
    f"SERVER={SQL_SERVER};"
    f"DATABASE={SQL_DATABASE};"
    f"Trusted_Connection=yes;"    # Windows Authentication - no password needed
)

def get_conn():
    return pyodbc.connect(SQL_CONN_STR)

def get_conn_autocommit():
    """Required for CREATE/DROP VECTOR INDEX - cannot run inside a transaction."""
    return pyodbc.connect(SQL_CONN_STR, autocommit=True)

Stockage des embeddings (Cellule 6)

Pour l’étape 6 de la cellule de stockage des embeddings, définissez les entrées, le responsable de l’étape et les critères d’arrêt avant de modifier le code. Les opérateurs doivent pouvoir relancer l’étape à partir d’un point de contrôle connu sans deviner l’état caché. Préférez des unités petites et testables plutôt que des scripts complexes. Lorsqu’une étape échoue, l’échec doit indiquer une seule responsabilité et non un processus embrouillé. Citez les passages qui ont réellement servi de base à la réponse. Sans citations, les opérateurs ne peuvent pas distinguer une hallucination d’un manque d’indexation. Pour l’étape 6 de la cellule de stockage des embeddings, définissez les entrées, le responsable de l’étape et les critères d’arrêt avant de modifier le code. Les opérateurs doivent pouvoir relancer l’étape à partir d’un point de contrôle connu sans deviner l’état caché. Enregistrez les temps d’exécution ainsi que le coût en tokens ou requêtes à côté des résultats fonctionnels. Une visibilité précoce du coût évite les factures inattendues lorsque le processus passe de la démonstration à l’environnement partagé.

import json

def vec_to_json(embedding: list[float]) -> str:
    return json.dumps(embedding)   # '[0.12, 0.34, ...]'

def store_in_sqlserver(chunks, embeddings, doc_name) -> int:
    drop_vector_index()    # must drop before any INSERT

    conn = get_conn()
    cur  = conn.cursor()

    sql = """
        INSERT INTO rag_chunks (source, chunk_index, content, embedding)
        VALUES (?, ?, ?, CAST(? AS VECTOR(768)))
    """
    for i, (chunk, emb) in enumerate(zip(chunks, embeddings)):
        cur.setinputsizes([
            (_pyodbc.SQL_WVARCHAR, 500, 0),
            _pyodbc.SQL_INTEGER,
            (_pyodbc.SQL_WVARCHAR, 0,   0),
            (_pyodbc.SQL_VARCHAR,  0,   0),   # ← must be VARCHAR, not NTEXT
        ])
        cur.execute(sql, (doc_name, i, chunk, vec_to_json(emb)))

    conn.commit()
    conn.close()
    create_vector_index()   # recreate after all inserts
    return len(chunks)

Récupération avec VECTOR_DISTANCE (Cellule 6, suite)

Lorsque vous travaillez sur l’étape de récupération via VECTORDISTANCE, notez d’abord les exigences : entrées requises, signal de succès et conséquences en cas d’échec partiel. Cette liste de contrôle permet de maintenir l’intégrité des modifications ultérieures du code. Conservez la configuration en dehors du code de l’application. Les fichiers d’environnement, les bases de données secrètes et les indicateurs fonctionnels doivent être regroupés en un seul endroit que les opérateurs peuvent auditer sans devoir lire l’ensemble du système. Évaluez le taux de rappel sur un ensemble de questions fixe avant d’ajuster les prompts. Un changement fréquent des prompts ne résout que rarement un système de récupération insuffisant.

def retrieve_context(query: str) -> list[dict]:
    query_embedding = embed_query(query)
    query_vec_json  = vec_to_json(query_embedding)

    conn = get_conn()
    cur  = conn.cursor()

    cur.setinputsizes([(_pyodbc.SQL_VARCHAR, 0, 0)])   # force VARCHAR for vector param

    cur.execute(f"""
        SELECT TOP ({TOP_K})
            content, source, chunk_index, ingested_at,
            VECTOR_DISTANCE('cosine', embedding, CAST(? AS VECTOR(768))) AS distance
        FROM rag_chunks
        ORDER BY distance ASC;
    """, (query_vec_json,))

    rows = cur.fetchall()
    conn.close()
    return [{"text": r[0], "source": r[1], "chunk_index": r[2],
             "ingested_at": str(r[3]) if r[3] else "",
             "score": round(1 - float(r[4]), 4)} for r in rows]

Problèmes spécifiques à SQL Server 2025 avec les vecteurs — Tous ceux que nous avons rencontrés

Lorsque vous travaillez sur l’étape Vector de SQL Server 2025, notez d’abord les exigences : entrées requises, signal de succès et ce qui se passe en cas d’échec partiel. Cette liste de contrôle permet de rester honnête lors des modifications ultérieures du code. Documentez à la fois le parcours normal et le parcours de récupération. Les tentatives répétées, les contrôles humains et la gestion des messages non traités font partie intégrante du produit, et non d’améliorations apportées ultérieurement. Mesurez le taux de rappel sur un ensemble fixe de questions avant d’ajuster les prompts. Un changement fréquent des prompts ne résout que rarement un système de récupération insuffisant.

Problème n°1 — Type d’objet inconnu ‘VECTOR’ dans l’instruction CREATE

Lorsque vous travaillez sur l’étape « Gotcha 1 : Objet inconnu », notez d’abord le contrat : les entrées requises, le signal de succès et ce qui se passe en cas d’échec partiel. Cette liste de contrôle permet de rester honnête lors des modifications ultérieures du code. Préférez des unités petites et testables plutôt que des scripts volumineux. Lorsqu’une étape échoue, l’échec doit indiquer une seule responsabilité et non un processus embrouillé. Mesurez le taux de rappel sur un ensemble fixe de questions avant d’ajuster les prompts. Un changement fréquent de prompts résout rarement un système de récupération insuffisant. Lorsque vous travaillez sur l’étape « Gotcha 1 : Objet inconnu », notez d’abord le contrat : les entrées requises, le signal de succès et ce qui se passe en cas d’échec partiel. Cette liste de contrôle permet de rester honnête lors des modifications ultérieures du code. Enregistrez les temps d’exécution ainsi que le coût en tokens ou en requêtes à côté des résultats fonctionnels. Une visibilité précoce des coûts évite les surprises financières lorsque le système passe de l’environnement de démonstration à des environnements partagés.

Msg 343, Level 15: Unknown object type 'VECTOR' used in CREATE, DROP, or ALTER statement.

Gotcha 2 — La clé primaire doit être une seule colonne INT de 4 octets

La phase relative à la clé primaire de Gotcha 2 fonctionne le mieux lorsqu’elle est considérée comme une surface mesurable. Recueillez un exemple réussi, un cas d’échec ainsi que la note de réversion avant d’élargir le périmètre. Conservez la configuration en dehors du code de l’application. Les fichiers d’environnement, les bases de données secrètes et les indicateurs fonctionnels doivent être regroupés en un seul endroit que les opérateurs peuvent auditer sans devoir lire l’ensemble du système. Séparez la politique de segmentation de la politique de récupération. Modifier l’une ne doit pas obliger à réécrire l’autre lorsque les métriques de qualité évoluent.

Msg 42217: Table must have a clustered primary key on a single 4 byte INT column to create a vector index.
-- ❌ Does not work with vector index
id  UNIQUEIDENTIFIER PRIMARY KEY DEFAULT NEWID()

-- ✅ Required
id  INT IDENTITY(1,1) PRIMARY KEY CLUSTERED

Gotcha 3 — Il est impossible d’INSERT/DELETE/UPDATE tant qu’un index vectoriel existe

The Gotcha 3 Cannot INSERT stage fonctionne le mieux lorsqu’il est considéré comme une surface mesurable. Capturez un enregistrement exemplaire, un cas d’échec et la note de réversion avant d’élargir le périmètre. Documentez ensemble le parcours normal et le parcours de récupération. Les tentatives répétées, les contrôles humains et la gestion des messages non livrés font partie intégrante du produit, et non d’une mise en forme ultérieure. Séparez la politique de segmentation de la politique de récupération : modifier l’une ne doit pas obliger à réécrire l’autre lorsque les métriques de qualité évoluent.

Msg 42231: Data modification statement failed because table 'rag_chunks' has a vector index on it.
def drop_vector_index():
    conn = get_conn_autocommit()   # autocommit required
    conn.cursor().execute("""
        IF EXISTS (
            SELECT 1 FROM sys.indexes
            WHERE name = 'idx_rag_embedding'
            AND object_id = OBJECT_ID('rag_chunks')
        )
        DROP INDEX idx_rag_embedding ON rag_chunks;
    """)
    conn.close()

def create_vector_index():
    conn = get_conn_autocommit()   # autocommit required
    conn.cursor().execute("""
        CREATE VECTOR INDEX idx_rag_embedding
            ON rag_chunks(embedding)
            WITH (METRIC = 'COSINE');
    """)
    conn.close()

Gotcha 4 — CREATE VECTOR INDEX ne peut pas s’exécuter à l’intérieur d’une transaction

La phase Gotcha 4 CREATE VECTOR fonctionne le mieux lorsqu’elle est considérée comme une surface mesurable. Capturez un exemple idéal, un cas d’échec et la note de réversion avant d’élargir le périmètre. Préférez des unités petites et testables plutôt que des scripts complexes. Lorsqu’une étape échoue, l’échec doit pointer vers une seule responsabilité et non vers un processus embrouillé. Séparez la politique de segmentation de la politique de récupération. Modifier l’une ne doit pas obliger à réécrire l’autre lorsque les métriques de qualité évoluent. La phase Gotcha 4 CREATE VECTOR fonctionne le mieux lorsqu’elle est considérée comme une surface mesurable. Capturez un exemple idéal, un cas d’échec et la note de réversion avant d’élargir le périmètre. Enregistrez les temps d’exécution ainsi que le coût des tokens ou des requêtes à côté des résultats fonctionnels. Une visibilité précoce des coûts évite des factures inattendues lorsque le processus passe de la démonstration aux environnements partagés.

Msg 574: CREATE VECTOR INDEX statement cannot be used inside a user transaction.
# ❌ Fails — implicit transaction
conn = pyodbc.connect(SQL_CONN_STR)
conn.cursor().execute("CREATE VECTOR INDEX ...")

# ✅ Works - no transaction wrapper
conn = pyodbc.connect(SQL_CONN_STR, autocommit=True)
conn.cursor().execute("CREATE VECTOR INDEX ...")

Gotcha 5 — La conversion explicite de ntext en vecteur n’est pas autorisée

Pour l’étape de conversion explicite de Gotcha 5, définissez les entrées, le responsable de l’étape et les critères d’arrêt avant de modifier le code. Les opérateurs doivent pouvoir relancer l’étape à partir d’un point de contrôle connu sans deviner l’état caché. Conservez la configuration en dehors du code de l’application. Les fichiers d’environnement, les bases de données secrètes et les flags fonctionnels doivent être regroupés en un seul endroit que les opérateurs peuvent auditer sans avoir à lire l’ensemble du graphe. Citez les passages qui justifient réellement la réponse. Sans citations, les opérateurs ne peuvent pas distinguer une hallucination d’un manque d’indexation.

Msg 529: Explicit conversion from data type ntext to vector is not allowed.
cur.setinputsizes([
    (_pyodbc.SQL_WVARCHAR, 500, 0),  # source — Unicode fine
    _pyodbc.SQL_INTEGER,              # chunk_index
    (_pyodbc.SQL_WVARCHAR, 0,   0),  # content — Unicode fine
    (_pyodbc.SQL_VARCHAR,  0,   0),  # embedding ← must be ASCII VARCHAR
])
cur.execute(sql, (doc_name, i, chunk, vec_to_json(emb)))

Gotcha 6 — VECTOR_SEARCH ne prend pas en charge les placeholders de paramètre ?

Pour Gotcha 6 VECTORSEARCH, il faut définir les entrées, le responsable de l’étape et les critères de fin avant de modifier le code. Les opérateurs doivent pouvoir relancer l’étape à partir d’un point de contrôle connu, sans avoir à deviner l’état caché. Documentez conjointement le parcours normal et les scénarios de récupération. Les tentatives répétées, les contrôles humains et la gestion des messages non traités font partie intégrante du produit, et non d’une amélioration ultérieure. Citez les passages qui justifient réellement la réponse. Sans citations, les opérateurs ne peuvent pas distinguer une hallucination d’un manque d’indexation.

Msg 102: Incorrect syntax near '('.
-- ❌ VECTOR_SEARCH with parameter placeholder — fails
FROM VECTOR_SEARCH(
    TABLE = rag_chunks USING VECTOR INDEX idx_rag_embedding,
    SIMILAR_TO = CAST(? AS VECTOR(768)),  -- pyodbc cannot pass ? here
    ...
)

-- ✅ VECTOR_DISTANCE - fully parameterized, GA, works perfectly
SELECT TOP (5)
    content,
    VECTOR_DISTANCE('cosine', embedding, CAST(? AS VECTOR(768))) AS distance
FROM rag_chunks
ORDER BY distance ASC;

PostgreSQL vs SQL Server — Côte à côte

Pour l’étape PostgreSQL vs SQL Server, définissez les entrées, le responsable de l’étape et les critères de fin avant de modifier le code. Les opérateurs doivent pouvoir relancer l’étape à partir d’un point de contrôle connu sans deviner l’état caché. Préférez des unités petites et testables à des scripts complexes. Lorsqu’une étape échoue, l’échec doit indiquer une seule responsabilité plutôt qu’un pipeline embrouillé. Citez les passages qui fondent réellement la réponse. Sans citations, les opérateurs ne peuvent pas distinguer une hallucination d’un manque d’indexation.

Ce que les deux bases de données ajoutent et que ChromaDB ne peut pas faire

Pour l’étape « Ce que les deux bases de données ajoutent », définissez les entrées, le responsable de l’étape et les critères de fin avant de modifier le code. Les opérateurs doivent pouvoir relancer l’étape à partir d’un point de contrôle connu sans deviner l’état caché. Considérez cette étape comme un contrat entre les entrées et les sorties validées. Nommez les artefacts, définissez des vérifications de succès et refusez toute exécution partielle silencieuse. Citez les passages qui ont réellement servi de base à la réponse. Sans citations, les opérateurs ne peuvent pas distinguer une hallucination d’un manque d’indexation.

-- PostgreSQL: Find chunks from documents belonging to a specific customer
SELECT c.content, 1 - (c.embedding <=> query_vec) AS score
FROM rag_chunks c
JOIN documents d ON d.filename = c.source
JOIN customers cu ON cu.id = d.customer_id
WHERE cu.tier = 'enterprise'
ORDER BY score DESC
LIMIT 5;
-- SQL Server: Same query, T-SQL syntax
SELECT TOP 5
    c.content,
    1 - VECTOR_DISTANCE('cosine', c.embedding, CAST(? AS VECTOR(768))) AS score
FROM rag_chunks c
JOIN documents d ON d.filename = c.source
JOIN customers cu ON cu.id = d.customer_id
WHERE cu.tier = 'enterprise'
ORDER BY score DESC;

Conclusion

Pour l’étape de conclusion, définissez les entrées, le responsable de l’étape et les critères d’arrêt avant de modifier le code. Les opérateurs doivent pouvoir relancer l’étape à partir d’un point de contrôle connu sans deviner l’état caché. Enregistrez les temps d’exécution ainsi que le coût des tokens ou des requêtes à côté des résultats fonctionnels. Une visibilité précoce du coût permet d’éviter des factures inattendues lorsque le processus passe de l’environnement de démonstration à des environnements partagés. Citez les passages qui ont réellement servi de base à la réponse. Sans citations, les opérateurs ne peuvent pas distinguer une hallucination d’un manque d’indexation.

Liste de contrôle opérationnelle