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