Gérer les bases via l'API
Vous pouvez tout piloter par API : créer des bases, y ajouter des documents et des extraits
(« chunks »), et faire de la recherche directe (sans modèle). La même clé sk- que pour les
appels au modèle authentifie ces requêtes.
export AUTH="Authorization: Bearer $ASTROLABE_API_KEY"export BASE="https://api.astrolabe.chat/v1/kb"Bases de connaissance
Section intitulée « Bases de connaissance »| Méthode | Route | Effet |
|---|---|---|
GET | /v1/kb | Lister vos bases |
POST | /v1/kb | Créer une base |
GET | /v1/kb/{slug} | Détail d’une base |
PATCH | /v1/kb/{slug} | Modifier (nom, slug, intention, facettes, type) |
DELETE | /v1/kb/{slug} | Supprimer (+ documents + chunks) |
GET | /v1/kb/{slug}/export | Exporter la base entière (bundle .kb.json.gz) |
POST | /v1/kb/import | Importer un bundle dans une base neuve |
# Créer une base, avec un schéma de facettes (structure des métadonnées)curl "$BASE" -H "$AUTH" -H "Content-Type: application/json" -d '{ "name": "FAQ Support", "intent": "FAQ : une question/réponse par extrait.", "facet_schema": { "facets": [ { "key": "theme", "values": ["facturation", "securite", "compte"] }, { "key": "montant", "type": "number", "description": "Montant TTC en euros, sans symbole." }, { "key": "resolu", "type": "boolean", "default": "false" } ]}}'# → { "slug": "faq-support", "name": "FAQ Support", "facet_schema": {…}, … }Le slug renvoyé est l’identifiant à utiliser partout ensuite (y compris dans
knowledge_base lors des appels RAG).
Champs d’une facette
Section intitulée « Champs d’une facette »Chaque entrée de facet_schema.facets contraint ce que l’IA doit extraire pour chaque
extrait lors du découpage piloté par l’IA :
| Champ | Type | Rôle |
|---|---|---|
key | string | Requis. Nom de la facette (ex. theme). |
type | string | Forme de la valeur : text (défaut), number, boolean, date (ISO AAAA-MM-JJ). Force l’IA à renvoyer un nombre/booléen plutôt qu’un texte approximatif. |
description | string | (optionnel) Intention : décrit en clair ce que l’IA doit mettre dans cette facette. |
values | string[] | (optionnel, type text uniquement) Valeurs autorisées → l’IA est contrainte à cet enum. |
default | string | (optionnel) Valeur de repli si l’IA ne sait pas trancher (coercée selon le type). |
Une facette sans type est traitée comme text (rétrocompatible). Les valeurs sont
ensuite filtrables à la recherche (knowledge_base_facets).
Type de base (role)
Section intitulée « Type de base (role) »À la création (POST /v1/kb) ou en modification (PATCH), une base a un role :
| Valeur | Usage |
|---|---|
data (défaut) | Base de données classique : des faits que l’IA restitue. |
process | Base de procédures (playbooks) : chaque document décrit, étape par étape, comment mener une analyse (quelles recherches lancer, quelles facettes filtrer, quoi vérifier). |
En mode agentique, quand une base process
est sélectionnée dans knowledge_base, l’IA la consulte d’abord pour trouver une fiche
correspondant à la demande, suit ses étapes sur les bases de données, et se replie
sur une démarche autonome si aucune fiche ne correspond. La sélection de la fiche reste à
l’IA (pas d’injection forcée). Voir le guide RAG pour un exemple complet.
# Créer une base de procédurescurl "$BASE" -H "$AUTH" -H "Content-Type: application/json" -d '{ "name": "Procédures — Fonds Impact", "role": "process", "intent": "Playbooks : comment mener nos analyses récurrentes."}'# → { "slug": "procedures-fonds-impact", "role": "process", … }Le role est renvoyé par GET /v1/kb et GET /v1/kb/{slug}. Une base sans role
explicite reste data.
Documents
Section intitulée « Documents »| Méthode | Route | Effet |
|---|---|---|
GET | /v1/kb/{slug}/documents | Lister les documents |
POST | /v1/kb/{slug}/documents | Ajouter un document — texte (JSON) ou fichier (multipart) |
GET | /v1/kb/{slug}/documents/{id} | Détail |
PATCH | /v1/kb/{slug}/documents/{id} | Modifier (filename, date, external_id, source_url) |
GET | /v1/kb/{slug}/documents/{id}/content | Lire le texte source (déchiffré) + état des chunks |
PUT | /v1/kb/{slug}/documents/{id}/content | Remplacer le texte source (sans re-découper) |
POST | /v1/kb/{slug}/documents/{id}/rechunk | Re-découper un document depuis sa source (régénère les chunks) |
POST | /v1/kb/{slug}/rechunk | Re-découper toute la base (job de fond, asynchrone) |
GET | /v1/kb/{slug}/rechunk | État du dernier re-découpage de la base |
DELETE | /v1/kb/{slug}/documents/{id} | Supprimer (+ ses chunks) |
{id} accepte l’UUID renvoyé ou votre external_id (voir plus bas).
# Ajouter du texte : il est découpé, vectorisé puis chiffrécurl "$BASE/faq-support/documents" -H "$AUTH" -H "Content-Type: application/json" -d '{ "title": "Crédits", "text": "Les crédits se rechargent dans le portail, par carte via Stripe.", "date": "2024-03-01", "external_id": "doc-credits", "source_url": "https://docs.astrolabe.chat/credits"}'# → { "id": "…", "external_id": "doc-credits", "filename": "Crédits",# "doc_date": "2024-03-01T00:00:00.000Z", "source_url": "https://docs.astrolabe.chat/credits",# "status": "ready", "chunk_count": 1 }Identifiant client (external_id) et upsert
Section intitulée « Identifiant client (external_id) et upsert »Plutôt que de retenir l’UUID interne, attachez votre propre external_id (unique par base)
à un document. Vous pouvez alors l’adresser directement (GET/PATCH/DELETE /v1/kb/{slug}/documents/{external_id}), et ré-ingérer le même document de façon idempotente :
# Même external_id → le document (et ses chunks) est REMPLACÉ (upsert)curl "$BASE/faq-support/documents" -H "$AUTH" -H "Content-Type: application/json" -d '{ "text": "Texte mis à jour…", "external_id": "doc-credits"}'# Puis : récupérer / modifier / supprimer par cet identifiantcurl "$BASE/faq-support/documents/doc-credits" -H "$AUTH"Date métier (date) et récence
Section intitulée « Date métier (date) et récence »Le date est la date du contenu (≠ created_at, la date d’insertion en base). Stockée comme
doc_date ; les chunks du document en héritent (chunk_date) sauf date explicite. Format
YYYY-MM-DD (ou ISO 8601). Exploitable à la recherche via date_range (voir plus bas) : un
filtre par plage de dates, là où les facettes ne permettent qu’une égalité.
Lien source (source_url)
Section intitulée « Lien source (source_url) »Attachez à un document un lien vers la ressource d’origine (page web, fichier dans un drive,
ticket…) via source_url. C’est purement informatif : stocké en clair, jamais vectorisé, et
remonté tel quel à la recherche directe et dans les
sources RAG — pour que votre process puisse
retrouver la ressource d’origine d’un extrait. Disponible à l’ajout (texte ou fichier) et
modifiable par PATCH (chaîne vide ou null pour le retirer).
Contenu source et re-découpage
Section intitulée « Contenu source et re-découpage »Chaque document conserve son texte source canonique (chiffré, comme les chunks). Les chunks en sont une projection régénérable : vous pouvez relire la source, la modifier, puis re-découper pour régénérer les chunks et leurs embeddings.
# Lire le texte source (déchiffré) + l'état des chunkscurl "$BASE/faq-support/documents/doc-credits/content" -H "$AUTH"# → { "id": "…", "filename": "Crédits", "has_source": true, "source_format": "markdown",# "source_origin": "original", "content": "Les crédits se rechargent…",# "chunk_count": 1, "chunks_stale": false }
# Remplacer le texte source (n'écrit PAS de chunks → ils deviennent « périmés »)curl -X PUT "$BASE/faq-support/documents/doc-credits/content" -H "$AUTH" -H "Content-Type: application/json" -d '{ "content": "Les crédits se rechargent dans le portail (carte via Stripe) ou par virement.", "format": "markdown"}'# → { …, "chunks_stale": true } ← la source a changé, re-découpez pour régénérer
# Variante « tout-en-un » : remplacer la source ET re-découper dans le même appelcurl -X PUT "$BASE/faq-support/documents/doc-credits/content" -H "$AUTH" -H "Content-Type: application/json" -d '{ "content": "Les crédits se rechargent dans le portail (carte via Stripe) ou par virement.", "rechunk": "simple"}'# → { …, "chunks_stale": false } ← source écrite + chunks régénérés en une requête
# Re-découper depuis la source : régénère les chunks (simple = gratuit ; ai = facturé)# `chunk_size` (200-6000, défaut 800) règle la taille cible des extraits du découpage simple.curl -X POST "$BASE/faq-support/documents/doc-credits/rechunk" -H "$AUTH" -H "Content-Type: application/json" -d '{ "chunking": "simple", "chunk_size": 1200}'# → le document (ApiDocument), "chunks_stale": falsesource_origin:original(source fidèle, captée à l’ingestion ou éditée) oureconstructed(source recollée depuis les chunks pour un document antérieur — pour un document découpé en IA, ce n’est pas l’original).chunks_stale:truequand la source a été modifiée depuis le dernier découpage — exposé aussi sur le détail du document (GET …/documents/{id}). Re-découpez pour le remettre à jour.POST …/rechunkacceptechunking("simple"par défaut, ou"ai"+chunk_model),chunk_size(200-6000, défaut 800 — taille cible des extraits du découpagesimple),facet_overrides(remplace ceux mémorisés à l’ingestion),bypass_conformite. Il remplace TOUS les chunks du document, y compris ceux ajoutés ou édités à la main.PUT …/contentaccepte une optionrechunk("simple"ou"ai", +chunk_model,chunk_size,facet_overrides,bypass_conformite) pour écrire la source ET régénérer les chunks en un seul appel (pratique pour l’automatisation). Sans elle, les chunks restent périmés jusqu’auPOST …/rechunk.chunk_sizeest aussi accepté à l’ingestion (POST …/documents, JSON ou champ multipart) pour fixer la taille des extraits du découpagesimpledu nouveau document.
Re-découper toute la base
Section intitulée « Re-découper toute la base »POST /v1/kb/{slug}/rechunk re-découpe tous les documents de la base depuis leur texte source.
Comme l’opération peut porter sur des centaines de documents (et, en ai, appeler un modèle par
document), elle s’exécute en job de fond : la requête enfile le travail et répond 202 avec
un job_id et l’état initial. Suivez l’avancée avec GET /v1/kb/{slug}/rechunk.
# Lancer le re-découpage de toute la base (asynchrone)curl -X POST "$BASE/faq-support/rechunk" -H "$AUTH" -H "Content-Type: application/json" -d '{ "chunking": "simple", "chunk_size": 1000}'# → 202 { "job_id": "…", "status": { "state": "running", "chunking": "simple", … } }
# Suivre l'avancée (polling)curl "$BASE/faq-support/rechunk" -H "$AUTH"# → { "status": { "state": "done", "total": 42, "rechunked": 40, "skipped": 2, "errors": 0, … } }- Body :
chunking("simple"par défaut, ou"ai"+chunk_model),chunk_size(mode simple). - Les documents sans texte source stocké ne sont pas re-découpables → comptés en
skipped. - Un seul re-découpage à la fois par base : relancer pendant qu’il tourne renvoie
409. - Métré : en
ai, chaque document appelle le modèle ; l’embedding des chunks est facturé (simple comme ai) sur la clé la plus récente du compte.
Forcer des facettes (facet_overrides)
Section intitulée « Forcer des facettes (facet_overrides) »Passez facet_overrides pour imposer des valeurs de facettes sur tous les chunks du document,
sans laisser l’IA les deviner :
# Découpage simple : catégorie fixée, l'IA n'intervient pascurl "$BASE/faq-support/documents" -H "$AUTH" -H "Content-Type: application/json" -d '{ "title": "CR réunion 15 jan.", "text": "…", "external_id": "cr-2025-01-15", "facet_overrides": { "categorie": "compte-rendu", "client": "Acme" }}'
# Découpage IA : l'IA ne remplit que les facettes non couvertes (ex. "date_reunion")curl "$BASE/faq-support/documents" -H "$AUTH" -H "Content-Type: application/json" -d '{ "title": "CR réunion 15 jan.", "text": "…", "chunking": "ai", "facet_overrides": { "categorie": "compte-rendu" }}'| Comportement | Détail |
|---|---|
Mode simple | Les overrides sont appliqués à chaque chunk, les facettes restantes restent {}. |
Mode ai | L’IA ne voit que les facettes non couvertes (tokens économisés) et sa sortie est fusionnée ; les overrides écrasent toujours ce qu’elle aurait produit. |
| Priorité | Les overrides gagnent sur les valeurs IA et sur les default du schéma. |
| Format | Objet JSON ou chaîne JSON (pour les clients no-code comme Make). |
Importer un fichier
Section intitulée « Importer un fichier »Le même endpoint accepte un fichier en multipart/form-data (champ file). Le texte est
extrait côté serveur puis découpé, vectorisé et chiffré comme un document texte. Le binaire
n’est pas conservé.
# Importer un PDF avec facettes forcéescurl "$BASE/faq-support/documents" -H "$AUTH" \ -F "file=@guide.pdf" \ -F "title=Guide d'installation" \ -F "source_url=https://drive.exemple.org/guide.pdf" \ -F 'facet_overrides={"categorie":"guide"}' \ -F "bypass_conformite=false"# → { "id": "…", "filename": "Guide d'installation", "source_type": "file", "status": "ready", "chunk_count": 12 }Formats : PDF, DOCX, TXT, MD, RTF, CSV, TSV, HTML (max 15 Mo). Le DOCX et le HTML sont
convertis en Markdown (titres, listes et tableaux préservés), ce qui améliore le découpage
(notamment le découpage IA) et l’extraction de facettes ; le PDF est extrait en texte à plat. Un
.doc (Word legacy) ou un PDF scanné (image, sans couche texte) est refusé (415/422) —
convertissez-le d’abord. Le champ title est optionnel (par défaut : le nom du fichier).
Chunks (extraits)
Section intitulée « Chunks (extraits) »| Méthode | Route | Effet |
|---|---|---|
GET | …/documents/{id}/chunks | Lister les extraits (déchiffrés, avec date et created_at) |
POST | …/documents/{id}/chunks | Ajouter un extrait à la main (content, facets?, date?) |
PATCH | …/chunks/{chunkId} | Modifier (content, facets et/ou date) |
DELETE | …/chunks/{chunkId} | Supprimer |
{id} du document = UUID ou external_id. Le date d’un extrait est optionnel : s’il est
absent, l’extrait hérite de la date du document.
# Ajouter un extrait précis, avec ses facettes et sa date métiercurl "$BASE/faq-support/documents/doc-credits/chunks" -H "$AUTH" -H "Content-Type: application/json" -d '{ "content": "Q : Quels paiements ? R : Carte bancaire et virement SEPA.", "facets": { "theme": "facturation" }, "date": "2024-03-01"}'Modifier le content d’un chunk le re-vectorise (facturé comme un embedding).
Recherche globale (toutes les bases)
Section intitulée « Recherche globale (toutes les bases) »POST /v1/search cherche dans toutes vos bases à la fois (au lieu d’une seule), sans IA, et
renvoie les documents correspondants. Pour aussi chercher dans une source externe (Airtable,
Notion, Pennylane…), synchronisez le connecteur dans une base (cf. Connecteurs API) : ses
enregistrements deviennent des documents et sont couverts par cette même recherche.
Deux régimes, via mode :
relevance(défaut siquery) : classement par pertinence sémantique. L’embedding de la question est facturé. Pagination superficielle (top_k), pas detotal.browse(défaut sansquery) : exploration — facettes + tri + recherche par titre (le textequeryfiltre alors le titre du document), avec pagination profonde (limit/offset) ettotalexact (nombre de documents, pas d’extraits). Aucun embedding facturé. C’est le régime « Algolia interne » : lister, filtrer, trier, paginer un jeu de documents.
# Pertinence : classement sémantiquecurl "https://api.astrolabe.chat/v1/search" -H "$AUTH" -H "Content-Type: application/json" -d '{ "query": "facture impayée Greenloop", "knowledge_base": ["factures", "crm"]}'
# Exploration : facettes + tri + pagination profondecurl "https://api.astrolabe.chat/v1/search" -H "$AUTH" -H "Content-Type: application/json" -d '{ "mode": "browse", "knowledge_base": ["factures"], "filters": [{ "key": "statut", "op": "in", "value": ["impayée"] }], "sort": { "by": "date", "dir": "desc" }, "limit": 25, "offset": 50}'{ "query": "", "mode": "browse", "searched": ["factures"], "total": 312, "offset": 50, "page_size": 25, "has_more": true, "results": [ { "knowledge_base": "factures", "document": "F-2023-6051 — GreenLoop", "external_id": "F-2023-6051", "date": "2023-04-11", "source_url": null, "matched_chunks": 3, "snippet": "…", "score": null } ], "sources": [ { "slug": "factures", "hits": 25 } ]}knowledge_base(slug ou tableau) : bases à interroger ; absent = toutes celles du compte.filters: mêmes filtres de facettes typés que la recherche par base (eq/neq/gt/gte/lt/lte/in/contains). Une base qui ne déclare pas une facette filtrée est écartée.sort(mode browse) :{ "by": "date" | "alpha", "dir": "asc" | "desc" }(alpha= titre). Ignoré en pertinence (classé par score).top_k(mode relevance, défaut 10, max 50) ;limit(mode browse, défaut 25, max 100) +offset(mode browse, pagination profonde).total: nombre exact de documents (mode browse) ;has_moreindique s’il reste des pages.- Compteurs au grain document : les facettes (
/v1/search/facetsci-dessous) comptent des documents, pas des extraits internes. - Facettes cross-bases :
POST /v1/search/facets(facettes filtrables, partagées d’abord) etPOST /v1/search/facet-values({ "key": "...", "query": "..." }→ valeurs contenant le texte).
Note (chiffrement) : le corps des documents est chiffré au repos → pas de recherche plein-texte par mots-clés sur le contenu. L’exploration recherche par titre/
external_id(en clair), et cette recherche est tolérante aux fautes (« recrutment » retrouve « recrutement »). Pour chercher dans le contenu, utilisez le mode pertinence (sémantique), lui aussi tolérant aux fautes (embeddings).
Recherche directe (sans IA)
Section intitulée « Recherche directe (sans IA) »Interroger une base sans passer par un modèle : utile pour intégrer la recherche dans vos propres workflows. Renvoie les extraits les plus proches, déchiffrés, avec leur score.
curl "$BASE/faq-support/search" -H "$AUTH" -H "Content-Type: application/json" -d '{ "query": "comment payer", "facets": { "theme": "facturation" }, "top_k": 5}'{ "group_by": "chunk", "data": [ { "n": 1, "knowledge_base": "faq-support", "document": "Crédits", "document_id": "a1b2c3d4-…", "external_id": "doc-credits", "source_url": "https://docs.astrolabe.chat/credits", "score": 0.87, "facets": { "theme": "facturation" }, "content": "Q : Quels paiements ? R : Carte bancaire et virement SEPA.", "relation": "semantic" } ]}La réponse contient toujours group_by (granularité utilisée) et data.
query optionnelle (mode listing)
Section intitulée « query optionnelle (mode listing) »query est facultative. Sans requête, la base bascule en mode listing : pas de recherche
sémantique, pas de score ("score": null), aucun coût d’embedding — uniquement le filtrage
(facets, filters, date_range) et le tri. Idéal pour « tous les documents tels que … » et
pour compter / lister de façon exacte (le listing est déterministe, là où la recherche
sémantique est approximative et plafonnée).
Chaque résultat porte aussi l’external_id du document (votre identifiant client, s’il est
renseigné) à côté de l’UUID interne.
Lookup exact par identifiant client (external_id)
Section intitulée « Lookup exact par identifiant client (external_id) »Quand vous connaissez la référence métier d’un document (n° de facture, id CRM, id de note…
c’est-à-dire votre external_id), ne partez pas en
recherche sémantique approximative : passez external_id pour restreindre la recherche aux
documents correspondants (une chaîne, ou un tableau). Combinable avec les autres paramètres.
# Retrouver un document précis par sa référence, et lire ses extraitscurl "$BASE/factures/search" -H "$AUTH" -H "Content-Type: application/json" -d '{ "external_id": "F-2025-089"}'# Plusieurs références d'un coupcurl "$BASE/factures/search" -H "$AUTH" -H "Content-Type: application/json" -d '{ "external_id": ["F-2025-089", "F-2025-090"], "group_by": "document"}'Pour lire le contenu entier d’un document dont vous avez l’external_id, vous pouvez aussi
appeler directement GET /v1/kb/{slug}/documents/{external_id} (l’UUID n’est pas nécessaire).
Recherche par titre (title_contains)
Section intitulée « Recherche par titre (title_contains) »Pour retrouver un document par son nom/titre (et non par le sens de son contenu), passez
title_contains : recherche sous-chaîne sur le titre (filename) et l’external_id, tolérante
aux fautes (« recrutment » retrouve « recrutement »). Se combine aux filtres, au tri, à la pagination
et au total analytique. Le contenu (chiffré) reste couvert par query (sémantique).
# Documents dont le titre ressemble à « recrutement », comptés au grain documentcurl "$BASE/rh/search" -H "$AUTH" -H "Content-Type: application/json" -d '{ "title_contains": "recrutment", "group_by": "document"}'Pagination (offset) et complétude (truncated)
Section intitulée « Pagination (offset) et complétude (truncated) »top_k est plafonné à 50. En régime analytique (sans query), le défaut est d’ailleurs
50 (on veut généralement tout l’ensemble, pas un top-k). Pour parcourir un ensemble plus grand
de façon exhaustive, utilisez offset (mode listing surtout) : offset: 0, puis 50, 100…
jusqu’à recevoir moins de top_k résultats.
La réponse porte truncated: true quand la page ne contient pas tout l’ensemble filtré
(total > nombre de lignes renvoyées) : ne concluez jamais « tous les X » sur une page tronquée
— paginez (ou lisez total pour un simple comptage). Pour lister les valeurs distinctes d’une
facette sous condition (toutes les entités concernées), GET …/facets avec le filtre en scope les
renvoie toutes d’un coup, sans pagination.
Granularité : extraits, documents ou dates (group_by)
Section intitulée « Granularité : extraits, documents ou dates (group_by) »"chunk"(défaut) : un résultat par extrait."document": les extraits d’un même document sont regroupés (le document n’apparaît qu’une fois) et classés par score moyen des extraits trouvés. Chaque résultat portedocument_id,matched_chunks(nombre d’extraits trouvés) etchunks(les extraits). C’est la granularité pour COMPTER ou LISTER des enregistrements (réunions, factures, dossiers… — un enregistrement = un document). En régime analytique (sansquery), la réponse portetotal= le compte exact de documents correspondant aux filtres, calculé côté serveur indépendamment detop_k/offset: pour « combien », liseztotal(inutile de paginer).datareste plafonné àtop_k(la page de résultats)."date": un résultat par date métier distincte (chunk_date), pour les questions par jour / chronologiques (combien de jours d’activité, occurrences par date). ⚠️ Plusieurs documents peuvent tomber le même jour :data.length= nombre de dates, ce qui sous-compte les enregistrements. La réponse expose donc, au niveau racine,distinct_documents(= nombre d’enregistrements, ≥ nombre de dates) et, par jour,documents(documents distincts ce jour-là), plustotal_dates(nombre de jours, analytique). Pour « combien de rendez-vous avec X en 2025 », utilisezgroup_by: "document"et liseztotal(oudistinct_documentsen modedate), jamais le nombre de dates.
curl "$BASE/faq-support/search" -H "$AUTH" -H "Content-Type: application/json" -d '{ "query": "comment payer", "group_by": "document", "top_k": 5}'{ "group_by": "document", "data": [ { "n": 1, "knowledge_base": "faq-support", "document": "Crédits", "document_id": "a1b2c3d4-…", "score": 0.86, "matched_chunks": 2, "chunks": [ { "n": 1, "content": "…", "score": 0.87, "facets": {} } ] } ]}En group_by: "date", la réponse porte distinct_documents (le nombre d’enregistrements) à
côté des dates — ici 12 jours mais 13 réunions, car deux ont eu lieu le même jour :
{ "group_by": "date", "total_dates": 12, "distinct_documents": 13, "data": [ { "n": 1, "date": "2025-05-21", "documents": 2, "matched_chunks": 8, "facets": { … }, "chunks": [ … ] } ]}État courant par entité (group_by: { facet, latest } + having)
Section intitulée « État courant par entité (group_by: { facet, latest } + having) »Pour les questions du type « combien de X sont actuellement Y » ou « le dernier état de
chaque X » (X = une entité : startup, client, fournisseur… ; Y = un état : en alerte, en retard,
churné…), passez un group_by objet : la base réduit à un résultat par entité, ramené à son
enregistrement le plus récent. La condition d’état courant se pose avec having (≠ filters,
voir l’encart) :
curl "$BASE/suivi-participations/search" -H "$AUTH" -H "Content-Type: application/json" -d '{ "group_by": { "facet": "startup", "latest": true }, "having": { "key": "alerte", "op": "eq", "value": true }}'{ "group_by": "entity", "entity_field": "startup", "total": 11, "data": [ { "entity": "AgriBoost", "date": "2025-06-02", "facets": { "startup": "AgriBoost", "statut": "…" } } ]}total= nombre d’entités distinctes dont le dernier enregistrement satisfaithaving— exact, indépendant detop_k.aggregatese combine :aggregate: "mrr_keur"donne alors la valeur d’état courant agrégée (ex. le MRR moyen actuel du portefeuille = moyenne du dernier MRR de chaque startup).
Filtres typés sur les facettes (filters)
Section intitulée « Filtres typés sur les facettes (filters) »En plus de facets (égalité simple), filters permet des comparaisons sur les valeurs de
facettes, validées contre le type déclaré de chaque facette (cf. facet_schema) :
curl "$BASE/factures/search" -H "$AUTH" -H "Content-Type: application/json" -d '{ "group_by": "document", "filters": [ { "key": "montant", "op": "gte", "value": 1000 }, { "key": "echeance", "op": "lt", "value": "2026-06-15" }, { "key": "categorie", "op": "in", "value": ["A", "B"] } ], "sort": { "by": "date", "dir": "desc" }}'Opérateurs : eq, neq, gt, gte, lt, lte, in, contains. Les opérateurs disponibles
dépendent du type de la facette — nombre/date : comparaisons ; texte : eq/neq/in/
contains ; booléen : eq/neq. Un filtre incohérent (ex. > sur un booléen) ou une valeur
incoercible est ignoré (jamais d’erreur). Les dates sont au format ISO AAAA-MM-JJ.
Agrégats numériques (aggregate)
Section intitulée « Agrégats numériques (aggregate) »Pour sommer / moyenner une facette numérique (« montant moyen des factures », « total facturé »,
« MRR cumulé »), passez aggregate = le nom d’une facette numérique, ou une liste de noms.
La réponse porte un champ aggregates : un objet par champ avec count, sum, avg, min,
max, calculés exactement côté serveur et par enregistrement (un document = une valeur ;
une facette dénormalisée sur plusieurs extraits n’est comptée qu’une fois), indépendamment de
top_k/pagination. Régime analytique uniquement (sans query).
curl "$BASE/factures/search" -H "$AUTH" -H "Content-Type: application/json" -d '{ "facets": { "sens": "client", "type_piece": "facture" }, "aggregate": "montant_ttc_eur"}'{ "group_by": "chunk", "total": 122, "aggregates": [ { "field": "montant_ttc_eur", "count": 122, "sum": 435155.91, "avg": 3566.85, "min": 484.13, "max": 12240 } ], "data": []}count = nombre d’enregistrements ayant une valeur numérique (le dénominateur de avg). Les
noms non numériques ou inconnus du schéma sont ignorés. N’additionnez jamais les montants à la
main : lisez aggregates.
Tri (sort)
Section intitulée « Tri (sort) »{ "by": …, "dir": "asc" | "desc" } — by vaut :
"relevance"(défaut avecquery) : par score décroissant. Exige une requête."date": par date métier (chunk_date), valeurs absentes en dernier."created": par date d’insertion en base (created_at) — l’ordre d’ajout, indépendant de la date métier."alpha": par nom de document.
Sans query, relevance retombe automatiquement sur date desc.
Plage de dates et « frères » par facettes
Section intitulée « Plage de dates et « frères » par facettes »Deux options avancées, identiques à celles du RAG :
date_range{ from?, to? }(ISO) : ne retient que les extraits dont la date métier (chunk_date) tombe dans la plage (les extraits sans date sont alors écartés).expand(requête uniquement) : en plus des hits sémantiques, ramène les extraits reliés par facettes (relation métier). Champs :seeds(graines, défaut 3),facets(clés de match ; omis = toutes),match("all"/"any"),depth(niveaux, défaut 1),max_chunks(plafond, défaut 30, max 200).
curl "$BASE/crm/search" -H "$AUTH" -H "Content-Type: application/json" -d '{ "query": "point sur le dossier", "date_range": { "from": "2024-01-01" }, "expand": { "seeds": 3, "facets": ["client"], "depth": 1 }}'Les extraits ramenés par relation portent "relation": "facet" (et leur level) ; les hits
sémantiques portent "relation": "semantic". ⚠️ L’expansion ajoute des extraits (donc des tokens
en aval) : max_chunks borne le total ; match: "any" + depth > 1 font grossir l’arbre vite.
Exporter et importer une base
Section intitulée « Exporter et importer une base »Une base entière s’exporte en bundle autonome (un fichier .kb.json.gz : métadonnées,
documents, extraits déchiffrés et vecteurs embarqués), puis se réimporte ailleurs — autre
compte, autre environnement, ou simple sauvegarde. Comme les vecteurs voyagent dans le bundle,
l’import est instantané et gratuit : aucun ré-embedding, aucun coût.
# Exporter → fichier gzip (le corps EST le .gz, ne pas le décompresser en transit)curl -L "$BASE/faq-support/export" -H "$AUTH" -o faq-support.kb.json.gz
# Importer → crée une base NEUVE (slug auto-dédupliqué, jamais d'écrasement)curl "$BASE/import" -H "$AUTH" \ -H "Content-Type: application/gzip" --data-binary @faq-support.kb.json.gz# → { "slug": "faq-support-2", "document_count": 12, "chunk_count": 240, … }L’envoi en corps brut (Content-Type: application/gzip ou application/json, --data-binary)
est streamé de bout en bout : le bundle n’est jamais chargé en entier en mémoire, ce qui permet
d’importer de grosses bases (plusieurs dizaines de milliers de documents) sans risque de
saturation. Un envoi multipart/form-data (champ file) reste accepté (pratique depuis un
formulaire). Nom et slug de la base créée sont repris du bundle ; on peut les surcharger via
?name= / ?slug= (ou les champs name / slug du formulaire).
| Point | Détail |
|---|---|
| Cible | L’import crée toujours une base neuve — il ne fusionne ni n’écrase une base existante. |
| Vecteurs | Embarqués (base64 float32). Un extrait sans vecteur (bundle fabriqué à la main) est ré-embeddé à l’import (coûte un embedding, requiert une clé). |
| Dimension | Le modèle d’embedding et la dimension (1024) doivent correspondre, sinon l’import est rejeté. |
| Taille | Bundle plafonné à 200 Mo (compressé). Import streamé (mémoire bornée) et par lots. Atomique : en cas d’erreur, aucune base partielle n’est créée. |
| Confidentialité | Le bundle contient votre texte en clair : traitez-le comme une donnée sensible. À l’import, une clé de chiffrement neuve est générée pour la nouvelle base. |
Au portail, ces actions sont les boutons « Exporter » (page d’une base) et « Importer » (liste des bases).
Valeurs de facettes (découverte)
Section intitulée « Valeurs de facettes (découverte) »Le facet_schema déclare la structure des facettes (et l’enum des valeurs autorisées pour les
facettes texte), mais pas les valeurs réellement présentes dans une base. Cette route les
expose — pratique pour construire une UI de filtres ou laisser un agent découvrir sur quoi
filtrer :
curl "$BASE/factures/facets" -H "$AUTH"{ "facets": [ { "key": "theme", "type": "text", "declared_values": ["facturation","securite"], "values": [ {"value":"facturation","count":2}, {"value":"securite","count":1} ], "distinct_count": 2, "truncated": false }, { "key": "montant", "type": "number", "min": 50, "max": 4200, "distinct_count": 3 }, { "key": "echeance", "type": "date", "min": "2024-03-01", "max": "2026-06-15", "distinct_count": 3 } ]}text/boolean:values= valeurs présentes triées par fréquence (count), plafonnées (cap 50) →truncated: trueau-delà ;distinct_count= nombre total de valeurs distinctes ;declared_valuesrappelle l’enum du schéma s’il existe.number/date:min,maxetdistinct_count(pas la liste complète).- Couvre toutes les clés rencontrées, y compris des facettes hors schéma.
- Gratuit (agrégation, pas d’embedding) ; le texte des extraits reste chiffré (seules les facettes, en clair, sont agrégées).
Restreindre à un sous-ensemble (scope) — superlatifs exacts
Section intitulée « Restreindre à un sous-ensemble (scope) — superlatifs exacts »Ajoutez un scope en query params (JSON encodé) — facets, filters, date_range — pour
n’agréger que les extraits correspondants. Les min/max portent alors sur ce filtre : c’est la
façon exacte et déterministe de répondre à un superlatif numérique (« le MRR le plus élevé de
X », « le montant max de la catégorie A »), sans scanner les extraits.
# min/max d'une facette numérique pour une entité donnéecurl "$BASE/factures/facets?facets=%7B%22startup%22%3A%22ReFarm%22%7D" -H "$AUTH"# → { "facets": [ { "key": "mrr_keur", "type": "number", "min": 18, "max": 127, … }, … ] }À savoir
Section intitulée « À savoir »- Facturation : les opérations qui vectorisent (ajout de texte/chunk, modification de contenu, recherche) coûtent un embedding. Le reste du CRUD (lister, renommer, supprimer, créer une base, lister les valeurs de facettes) est gratuit.
- Sécurité : le texte des extraits est chiffré au repos (une clé par base) ; il n’est déchiffré que pour vous répondre.
- Codes :
401clé invalide ·404base/document/chunk introuvable ·400requête invalide.