RAG et bases de connaissances
Comment Cradle ancre les réponses de l'agent dans vos propres documents — chunking, embedding, retrieval, citations et crawl web.
Cradle ne répond pas à partir d'un modèle cloud générique. Quand un agent a une ou plusieurs bases de connaissances, le message entrant est embeddé, mis en correspondance avec le stockage vectoriel local, et les meilleurs extraits sont injectés dans le prompt avec des citations inline.
Flux de bout en bout
Message utilisateur
│
▼
TriageOrchestrator
├── évaluation du risque
├── routeur choisit un agent
└── si agent.knowledgeBaseIds.length > 0 :
├── acquireEmbedder()
├── search(query, embedder, { kbIds, topK: 6 })
│ embed(query) → vecteur
│ sqlite-vec MATCH → meilleurs chunks
│ jointure documents + knowledge_bases
└── buildPrompt(agent, turns, sources)
«Utilisez les sources numérotées ci-dessous si pertinent. Citez comme [1].»
[1] Titre du document (URI source)
contenu...
[2] ...
│
▼
runner.generate → brouillon avec citations → Boîte de réception / réponse automatiqueStockage vectoriel
Cradle utilise sqlite-vec pour la recherche vectorielle. Chaque base de connaissances obtient sa propre table virtuelle dimensionnée selon la dimension du modèle d'embedding :
CREATE VIRTUAL TABLE vec_kb_<kbId> USING vec0(
chunk_id TEXT PRIMARY KEY,
embedding FLOAT[<dim>]
);Les anciennes bases de connaissances sans entrée de registre continuent de fonctionner via l'ancienne table partagée vec_chunks.
Tables
knowledge_bases (id, name, description, embedding_model_id, embedding_dim, created_at)
documents (id, kb_id, source_kind, source_uri, title, mime, sha256,
status, char_count, chunk_count, error_message, ingested_at)
document_chunks (id, document_id, kb_id, position, content, token_count, metadata)agents.knowledge_base_ids est une liste JSON d'IDs de bases de connaissances que l'agent peut utiliser.
Stratégie de chunking
Le chunker paragraphe-first garde le texte lié ensemble :
- Découper sur les lignes vides pour obtenir les paragraphes.
- Accumuler les paragraphes tant que le total courant reste sous 2 000 caractères.
- Si un paragraphe dépasse la limite, découper aux limites de phrases.
- Si une seule phrase dépasse encore la limite, coupure dure par caractères.
- Préserver un chevauchement de 200 caractères entre les chunks consécutifs.
Chaque chunk stocke content, position, et un nombre de tokens estimé (chars / 4).
Ingestion
await ingestText({
kbId,
title,
text,
sourceKind: 'upload' | 'url' | 'crawl' | 'inline',
sourceUri,
mime,
embedder,
batchSize = 16,
})- Idempotence par SHA-256 du texte.
- Les chunks sont insérés comme
indexing, embeddés par lots, puis marquésready. - Les erreurs font un rollback vers
status='error'avec le message conservé. - La progression est rapportée entre les lots.
Parsers
| Extension | Implémentation |
|---|---|
.md, .markdown | lu comme texte brut |
.txt, .log, .csv | lu comme texte brut |
.pdf | pdf-parse v2 |
.docx | mammoth.extractRawText |
.xlsx, .xls | exceljs, une ligne de texte par ligne |
parseFile(filePath) renvoie { text, title, mime }. isSupported(filePath) vérifie l'extension avant de tenter l'ingestion.
Retrieval
await search(query, embedder, { kbIds, topK = 6, maxDistance })- Embedder la requête.
- Exécuter
SELECT chunk_id, distance FROM vec_kb_... WHERE embedding MATCH ?. - Joindre
document_chunksetdocumentspour enrichir les résultats. - Filtrer aux bases de connaissances demandées.
- Filtrer optionnellement par
maxDistance. - Renvoyer
topKrésultats sous forme deSearchHit[].
Les helpers deleteDocument(id) et deleteKnowledgeBase(id) nettoient les chunks en cascade. Les cascades de clés étrangères Drizzle couvrent la plupart des tables, mais les tables virtuelles sqlite-vec sont nettoyées explicitement.
Citations
Chaque chunk utilisé dans une réponse est sauvegardé dans ticket_sources avec un snapshot :
ticket_sources (
id, ticket_id, message_id, position,
document_id, chunk_id, kb_id,
document_title, source_uri,
content_preview, content_full,
chunk_position, distance, created_at
)Les snapshots rendent les tickets reproductibles même si le document original est ensuite supprimé ou ré-ingéré. L'interface affiche des badges numérotés correspondant aux références [1], [2], ... que le modèle a placées dans le texte.
Crawl web
Les bases de connaissances peuvent être alimentées par un crawl web :
rag:ingestUrl({ kbId, url })— une page ou un document.rag:crawlSite({ kbId, seedUrl, maxPages, maxDepth, sameDomain, followDocs, respectRobots })— parcours BFS avec progression en direct.
Les modules du crawler vivent dans src/core/rag/crawler/ :
url.ts— normalisation d'URL et vérifications de même domaine.html.ts— extraction cheerio + turndown, scripts et navigation retirés.robots.ts— cache robots.txt par origine.fetcher.ts— récupère HTML ou documents.crawler.ts— BFS avec délai de politesse (500 ms), limites de profondeur/pages et suivi de documents.
Limites par défaut : HTML jusqu'à 10 Mo, documents jusqu'à 50 Mo, timeout de requête 20 s.
RAG + fine-tuning
Le RAG fournit des faits (prix, spécifications, clauses). Le fine-tuning fournit le style et les patterns de raisonnement. Les deux fonctionnalités se complètent ; voir finetune pour le workflow d'entraînement.