Construire un assistant IA connecté à vos connaissances
Créer un SaaS assistant IA capable d’utiliser n’importe quelle knowledge base n’est pas un simple branchement à une API de modèles. Ce guide présente une méthode de bout en bout basée sur le RAG (Retrieval Augmented Generation) avec embeddings, similarité cosinus, Postgres et pgvector, ainsi que des exemples concrets de pipeline d’ingestion et de requêtage. L’objectif est d’obtenir un assistant IA fiable, traçable, rapide et prêt pour la mise en production.
Cela peut etre un cas d'usage parfait pour un accès à une base documentaire, la recherche de maillage interne dans le blog d'un saas, un assistant conversationnel spécialisé sur un domaine particulier, ...
Pourquoi un assistant IA avec base de connaissance
Un assistant IA connecté à une base de connaissance interne ou externe apporte trois avantages compétitifs majeurs pour un SaaS:
-
Précision: la réponse s’appuie sur des sources contrôlées plutôt que sur une génération purement statistique.
-
Traçabilité: chaque passage cité peut être relié à son titre et à son lien source.
-
Scalabilité: de nouvelles sources peuvent être ingérées sans refondre l’architecture.
Cette approche répond aux exigences d’un public B2B: conformité, cohérence pédagogique, et gouvernance des contenus.
Architecture logique du projet
L’assistant IA suit un pipeline clair:
-
KB: la connaissance est maintenue dans des fichiers Markdown avec front matter (titre, URL).
-
Ingestion: découpage en chunks, calcul des embeddings, stockage dans Postgres.
-
Retrieval: pour chaque question, on génère l’embedding de la requête et on calcule une similarité cosinus contre tous les chunks afin d’extraire les Top K passages pertinents.
-
Contexte: les extraits sont formatés en blocs A1..Ak avec titre et source.
-
Génération: le modèle reçoit la consigne, l’historique utile et les blocs de contexte pour produire une réponse ancrée.
-
Streaming: la réponse est renvoyée au fil de l’eau pour une meilleure UX.
-
UI: l’interface affiche la conversation, gère un mode KB stricte, un seuil minSim et un Top K.
Les fondamentaux IA: embeddings, vecteurs et cosinus
Un embedding transforme un texte en vecteur numérique de grande dimension. Deux textes proches en sens auront des vecteurs proches. La similarité cosinus mesure l’angle entre deux vecteurs:
cosine(a,b) = ( Σ a_i b_i ) / ( ||a|| * ||b|| )
-
1.0 = très similaire
-
0.0 = sans rapport
-
valeurs négatives = opposées
Pour un assistant IA, les embeddings doivent être consistants entre documents et questions. Un choix pragmatique est text-embedding-3-small d’OpenAI, bon compromis précision/coût.
Ressources utiles:
La knowledge base: format et hygiène
Les contenus sont stockés en Markdown pour rester éditables et réutilisables. Chaque fichier contient un front matter:
---
title: Construire un budget
source_url: https://exemple.local/budget
---
Méthode 50-30-20 comme base. Adapter selon charges fixes. Automatiser l’épargne. Revue hebdomadaire.
Recommandations:
-
Utiliser des titres explicites et une source_url stable.
-
Éviter les doublons et tenir une version du document.
-
Privilégier des paragraphes denses et pédagogiques plutôt que des phrases orphelines.
Ingestion: découper, vectoriser, stocker
Découpage en chunks
Objectif: produire des segments autonomes et suffisamment courts pour rester pertinents.
-
Taille: 800 à 2000 caractères.
-
Chevauchement: 10 à 20 % pour ne pas couper une idée en deux.
-
Heuristique: couper de préférence sur une ponctuation forte ou un titre.
Embeddings documents
-
Modèle recommandé: text-embedding-3-small.
-
Encodage stable, nettoyage léger.
-
Batching raisonnable pour éviter les erreurs mémoire.
-
Stockage immédiat en base avec métadonnées:
title
,source_url
,doc_id
,tokens
,created_at
.
Schéma Postgres minimal
CREATE TABLE IF NOT EXISTS knowledge_chunks (
id UUID PRIMARY KEY,
doc_id TEXT NOT NULL,
title TEXT,
source_url TEXT,
content TEXT NOT NULL,
tokens INTEGER NOT NULL,
embedding DOUBLE PRECISION[] NOT NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
Similarité cosinus côté SQL
CREATE OR REPLACE FUNCTION array_dot(a double precision[], b double precision[])
RETURNS double precision AS $$
SELECT COALESCE(SUM(a[i]*b[i]),0)
FROM generate_subscripts(a,1) g(i)
WHERE i <= cardinality(b)
$$ LANGUAGE sql IMMUTABLE;
CREATE OR REPLACE FUNCTION array_norm(a double precision[])
RETURNS double precision AS $$
SELECT sqrt(COALESCE(SUM(x*x),0)) FROM unnest(a) t(x)
$$ LANGUAGE sql IMMUTABLE;
CREATE OR REPLACE FUNCTION cosine_similarity(a double precision[], b double precision[])
RETURNS double precision AS $$
SELECT CASE
WHEN array_norm(a)=0 OR array_norm(b)=0 THEN 0
ELSE array_dot(a,b)/(array_norm(a)*array_norm(b))
END
$$ LANGUAGE sql IMMUTABLE;
Retrieval: Top K, seuil minSim et mode KB stricte
Requête de base
SELECT id, title, source_url, content,
cosine_similarity(embedding, $1::double precision[]) AS sim
FROM knowledge_chunks
ORDER BY sim DESC
LIMIT $2;
-
$1
est l’embedding de la question. -
$2
est le Top K désiré.
Réglages clés
-
Top K: 4 à 8 par défaut.
-
minSim: 0.68 à 0.75 pour filtrer les extraits faibles.
-
Mode KB stricte: si aucun chunk n’est au dessus de
minSim
, retourner une réponse de type hors KB sans appeler la génération ouverte.
Réduire la redondance
Appliquer une heuristique MMR simple: après tri par cosinus, ne conserver un nouvel extrait que s’il n’est pas trop similaire à ceux déjà retenus. Cela augmente la diversité des angles couverts.
Index vectoriels: quand passer à pgvector
Le fallback en double precision[]
convient pour des volumes modestes. Au delà, il est recommandé d’utiliser pgvector:
CREATE EXTENSION IF NOT EXISTS vector;
ALTER TABLE knowledge_chunks
ALTER COLUMN embedding TYPE vector(1536)
USING embedding::vector(1536);
CREATE INDEX ON knowledge_chunks
USING ivfflat (embedding vector_cosine_ops) WITH (lists = 100);
-
IVFFLAT: rapide et simple à paramétrer.
-
HNSW: qualité et latence excellentes au prix d’un coût d’indexation plus élevé.
Tableau comparatif:
Option | Latence | Rappel | Complexité | Cas d’usage |
---|---|---|---|---|
Postgres array + scan | élevée | parfait | faible | POC, petits corpus |
pgvector IVFFLAT | faible | bon | moyen | production standard |
pgvector HNSW | très faible | très bon | élevé | grands corpus, SLA stricts |
Construction du contexte et règles de génération
Les extraits retenus sont formatés en blocs A1..Ak:
A1 • Titre • URL
Contenu...
---
A2 • Titre • URL
Contenu...
Le prompt impose:
-
Répondre en français, avec un rôle éducatif.
-
Citer les passages utilisés sous forme [A1], [A2].
-
En mode strict, ne répondre que depuis A1..Ak.
- Ici tout autre info qui vous semble pertinente pour votre prompt système
Exemple d’API de génération en streaming
Côté serveur
import OpenAI from "openai";
import { NextResponse } from "next/server";
export const runtime = "nodejs";
export async function POST(req: Request): Promise<Response> {
const body = await req.json();
const { userInput, conversation, topK = 6, strictKb = true, minSim = 0.7 } = body;
// 1) Retrieve rows = retrieveTopK(userInput, topK, { minSim, strictKb })
// 2) build context blocks
const context = /* buildContextBlocks(rows) */ "";
const openai = new OpenAI({ apiKey: process.env.OPENAI_API_KEY });
const stream = await openai.chat.completions.create({
model: "gpt-4o-mini",
stream: true,
temperature: 0.2,
max_tokens: 900,
messages: [
{ role: "system", content: "Assistant financier éducatif. Citez [A1]..." },
...(conversation ?? []),
{ role: "user", content: `Question:\n${userInput}\n\nContexte:\n${context}` },
],
});
const encoder = new TextEncoder();
const readable = new ReadableStream<Uint8Array>({
async start(controller) {
try {
for await (const part of stream) {
const delta = part.choices?.[0]?.delta?.content;
if (delta) controller.enqueue(encoder.encode(delta));
}
controller.close();
} catch (e) {
controller.error(e);
}
}
});
return new NextResponse(readable, {
headers: { "Content-Type": "text/plain; charset=utf-8", "Cache-Control": "no-cache" }
});
}
Côté client
const controller = new AbortController();
const res = await fetch("/api/ask", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ userInput, topK, strictKb, minSim }),
signal: controller.signal,
});
const reader = res.body?.getReader();
const decoder = new TextDecoder();
let full = "";
while (reader) {
const { value, done } = await reader.read();
if (done) break;
const chunk = decoder.decode(value || new Uint8Array(), { stream: true });
full += chunk;
setStreaming(full); // affichage progressif
}
// bouton Stop
function stop() { controller.abort(); }
Traçabilité et conformité
La traçabilité via [A1] et [A2] permet d’expliquer chaque élément d’une réponse. En contexte finance, c’est un argument de crédibilité et de conformité. La présence d’un mode KB stricte limite les hallucinations et garantit que l’assistant IA ne répond que lorsque la knowledge base couvre la demande.
Paramétrage de qualité et coût
-
Top K: agir sur la couverture. Plus de K peut apporter de nuances mais augmente coûts et latence.
-
minSim: agir sur la précision. Plus élevé signifie réponses plus concentrées mais risque de refus.
-
Reranking: réordonner un lot élargi avec un cross encoder si la qualité doit être maximale.
-
Caching: mémoriser l’embedding de questions fréquentes.
-
Indexation: passer à pgvector sur croissance du corpus.
Exemple de calcul rapide
-
1 embedding question + contexte de K chunks + génération finale.
-
Coût variable selon le modèle et la longueur. En phase pilote, privilégier un modèle léger comme gpt-4o-mini.
-
Mesurer le Recall@K sur un jeu de requêtes internes pour valider les réglages.
Gouvernance du contenu
-
Ré-ingestion propre: supprimer les anciens chunks d’un document avant de réimporter.
-
Versionner le
doc_id
avec hash de contenu. -
Qualité rédactionnelle: éviter les coquilles, harmoniser le style, mettre à jour les chiffres.
-
Sécurité: ne pas stocker de PII dans la KB. Logger les événements sans conserver d’éléments sensibles.
Intégrations et extensions
-
Recherche hybride: combiner mots-clés BM25 et vecteur pour des requêtes factuelles.
-
MMR: éviter la redondance dans le contexte pour améliorer la pertinence globale.
-
Citations enrichies: relier [A1] à la
source_url
cliquable. -
Observabilité: suivre latences, taux de refus en mode strict, distribution des scores de similarité.
Exemple d’ingestion par lot
Pseudocode d’ingestion Markdown vers Postgres:
import matter from "gray-matter";
import OpenAI from "openai";
import { Pool } from "pg";
import fs from "node:fs";
const pool = new Pool({ connectionString: process.env.DATABASE_URL });
const openai = new OpenAI({ apiKey: process.env.OPENAI_API_KEY });
async function ingestFile(path: string) {
const raw = fs.readFileSync(path, "utf8");
const { data, content } = matter(raw);
const chunks = splitIntoChunks(content, 1800, 200); // taille, overlap
const client = await pool.connect();
try {
await client.query("BEGIN");
// purge by doc_id or title if needed
for (const c of chunks) {
const emb = await openai.embeddings.create({
model: "text-embedding-3-small",
input: c,
});
const vector = emb.data[0].embedding;
await client.query(
`INSERT INTO knowledge_chunks (id, doc_id, title, source_url, content, tokens, embedding)
VALUES (gen_random_uuid(), $1, $2, $3, $4, $5, $6)`,
[hash(path), data.title ?? null, data.source_url ?? null, c, estimateTokens(c), vector]
);
}
await client.query("COMMIT");
} catch (e) {
await client.query("ROLLBACK");
throw e;
} finally {
client.release();
}
}
Tableau de route conseillé
Phase | Objectif | Livrables | Indicateurs |
---|---|---|---|
1 | POC local | Ingest Markdown, retrieve + génération | Latence, cohérence citations |
2 | Qualité | Règlages Top K, minSim, MMR | Recall@K, taux hors KB |
3 | Scalabilité | pgvector + index | P95 latence, coût moyen |
4 | Production | Observabilité, sécurité, SLA | Uptime, erreurs, usage |
Pour créer un SaaS sans se prendre la tête
Pour avancer plus vite, il est possible de s’appuyer sur une base de connaissances et des check-lists structurées. La todo-list et la knowledge base proposées par SaaS Path sont gratuites et découpées en modules progressifs. Pour découvrir une méthode pragmatique sur comment créer un SaaS, le site rassemble des ressources techniques, marketing et SEO pour accélérer un lancement.
Conclusion
Créer un SaaS assistant IA connecté à n’importe quelle knowledge base exige plus qu’un simple appel de modèle. La combinaison RAG + embeddings + Postgres/pgvector fournit une base technique robuste, traçable et scalable. Avec des réglages pertinents de Top K et de minSim, un mode KB stricte, une ingestion propre et une UX réactive, le produit délivre une valeur pédagogique solide et différenciante.