Architecture

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 automatique

Stockage 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 :

  1. Découper sur les lignes vides pour obtenir les paragraphes.
  2. Accumuler les paragraphes tant que le total courant reste sous 2 000 caractères.
  3. Si un paragraphe dépasse la limite, découper aux limites de phrases.
  4. Si une seule phrase dépasse encore la limite, coupure dure par caractères.
  5. 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és ready.
  • Les erreurs font un rollback vers status='error' avec le message conservé.
  • La progression est rapportée entre les lots.

Parsers

ExtensionImplémentation
.md, .markdownlu comme texte brut
.txt, .log, .csvlu comme texte brut
.pdfpdf-parse v2
.docxmammoth.extractRawText
.xlsx, .xlsexceljs, 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 })
  1. Embedder la requête.
  2. Exécuter SELECT chunk_id, distance FROM vec_kb_... WHERE embedding MATCH ?.
  3. Joindre document_chunks et documents pour enrichir les résultats.
  4. Filtrer aux bases de connaissances demandées.
  5. Filtrer optionnellement par maxDistance.
  6. Renvoyer topK résultats sous forme de SearchHit[].

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.