Aller au contenu

API AI Query — cubes OLAP

L’API AI Query permet à un agent de requêter un cube OLAP Mondrian sans jamais voir ni écrire de MDX. L’agent récupère un schéma auto-descriptif, remplit une requête JSON à partir de celui-ci, le serveur valide chaque nom, exécute et retourne des records typés. Si un nom est incorrect, l’erreur dit à l’agent exactement quoi corriger — aucun prompt engineering requis.

C’est le pendant OLAP de l’ API AI Query pour les modèles Ossie (la surface SQL/YAML-sémantique). Même discipline, mêmes garde-fous ; celle-ci travaille contre des cubes Mondrian au lieu de YAML Ossie. Assise par-dessus, la couche en langage naturel /ai/ask — l’endpoint ask traduit une question en anglais simple en exactement le corps de requête documenté ici.

Orientation rapide

Trois endpoints couvrent ~90 % de l’usage des agents :

EndpointObjectif
GET /rest/saiku/api/ai/cubesListe chaque cube que l’appelant peut requêter.
GET /rest/saiku/api/ai/schema/{cubeId}Schéma auto-descriptif — mesures, dimensions, hiérarchies, niveaux, membres d’échantillon, synonymes, et le JSON Schema de la requête.
POST /rest/saiku/api/ai/queryExécute une requête typée. Réponse au format records par défaut ; matrix avec ?format=matrix.

Endpoints de longue traîne (documentés sur les pages liées) :

EndpointObjectif
GET /rest/saiku/api/ai/members/searchRecherche par sous-chaîne de membres sur un niveau.
POST /rest/saiku/api/ai/scenario/whatifSimulation what-if en write-back.
POST /rest/saiku/api/ai/query/execute-asyncSoumet pour exécution en arrière-plan (interroge status / result).
POST /rest/saiku/api/ai/anomalyExécute la requête puis signale les anomalies le long d’un axe temporel.
POST /rest/saiku/api/ai/forecastProjette des points futurs (ETS / ARIMA / Prophet).
POST /rest/saiku/api/ai/askQuestion en langage naturel. Nécessite une clé LLM.

Le cubeId partout est le quadruplet connection/catalog/schema/cubeName joint par /. Tous les endpoints nécessitent une session authentifiée ; les endpoints POST nécessitent la paire cookie/en-tête CSRF.

Étape 1 — lister les cubes

GET /rest/saiku/api/ai/cubes
[
{
"connectionName": "unknown_foodmart",
"catalog": "FoodMart",
"schema": "FoodMart",
"cubeName": "Sales",
"cubeCaption": "Sales",
"defaultMeasure": "Unit Sales",
"measureCount": 8
}
]

Le quadruplet connectionName/catalog/schema/cubeName est l’identifiant de cube utilisé partout ailleurs.

Étape 2 — récupérer le schéma typé

GET /rest/saiku/api/ai/schema/unknown_foodmart/FoodMart/FoodMart/Sales

N’encodez pas les slashes en URL — le template de chemin accepte directement la forme multi-segment connection/catalog/schema/cubeName. La réponse est dense — c’est ce qui rend l’API auto-descriptive :

{
"cubeId": "unknown_foodmart/FoodMart/FoodMart/Sales",
"measures": {
"store sales": {
"name": "Store Sales",
"uniqueName": "[Measures].[Store Sales]",
"description": "Net retail revenue in USD across all transactions.",
"synonyms": ["revenue", "turnover", "top-line"], // accepted as `name` on input
"unit": "USD",
"aggregationKind": "sum"
}
// …8 measures total…
},
"measureAliases": { "revenue": "store sales", "turnover": "store sales" },
"dimensions": {
"time": {
"name": "Time", "uniqueName": "[Time]",
"hierarchies": {
"time by": {
"name": "Time By",
"levels": {
"quarter": {
"name": "Quarter",
"synonyms": ["quarterly", "qtr"], // accepted as `level` on input
"sampleMembers": [
{ "caption": "Q1", "uniqueName": "[Time].[Time By].[Quarter].&[Q1]" }
]
}
}
}
}
}
}
}

Les synonymes et alias de nom d’affichage sont acceptés en entrée partout où le name canonique l’est — un agent peut dire "revenue" et le serveur le résout en Store Sales.

Étape 3 — exécuter une requête

« Montre Store Sales et Unit Sales par Product Family, top 3 par Store Sales. »

POST /rest/saiku/api/ai/query
Content-Type: application/json
{
"cube": "unknown_foodmart/FoodMart/FoodMart/Sales",
"measures": [{ "name": "Store Sales" }, { "name": "Unit Sales" }],
"rows": [{ "dimension": "Product", "hierarchy": "Products", "level": "Product Family" }],
"order": [{ "by": "Store Sales", "direction": "desc" }],
"limit": 3
}

cube accepte soit la forme objet à 4 segments, soit la chaîne compacte "connection/catalog/schema/cube".

Réponse (200) :

{
"status": "SUCCESS",
"format": "records",
"metadata": {
"generatedMdx": "SELECT NON EMPTY {[Measures].[Store Sales], [Measures].[Unit Sales]} ON COLUMNS, NON EMPTY TopCount([Product].[Products].[Product Family].Members, 3, [Measures].[Store Sales]) ON ROWS FROM [Sales]",
"freshness": { "computedAtMillis": 1715798421042, "cached": false }
},
"data": [
{
"Product Family": "Food",
"Store Sales": { "value": 409035.59, "formatted": "409,035.59", "unit": null },
"Unit Sales": { "value": 191940.0, "formatted": "191,940", "unit": null }
}
],
"totalRows": 3,
"runtimeMs": 421
}

Chaque ligne est un objet auto-descriptif clé par la légende de colonne lisible par l’humain. Chaque cellule numérique est une enveloppe typée :

  • value — nombre analysé (pour les maths / le tri / le graphique)
  • formatted — la chaîne d’affichage pré-formatée de Mondrian (pour l’UI)
  • unit — reniflée depuis la chaîne formatée (USD, GBP, EUR, JPY, %) ou null

generatedMdx est renvoyé en écho pour le débogage ; les agents l’ignorent généralement.

Format matrix

Les clients indexés par position se désengagent des records avec ?format=matrix — la réponse porte matrix au lieu de data, chaque ligne clée par l’index de colonne sous forme de chaîne, les cellules restant l’enveloppe typée {value, formatted, unit}.

Confidentialité : k-anonymat

Quand ai.kAnonymity est défini (défaut 5 ; 0 désactive), le serveur masque les valeurs de mesure des petites cellules avant que le résultat ne franchisse la frontière AI — toute ligne dont la mesure de comptage dans le résultat tombe sous k voit ses cellules de mesure masquées avec suppressed: true. S’applique aux records et matrix, et aux endpoints dérivés /ai/anomaly + /ai/forecast.

Étape 4 — la validation enseigne à l’agent

Fournissez un nom qui ne se résout pas et le serveur retourne 400 avec un corps à partir duquel l’agent peut s’auto-corriger — aucun prompting de réessai nécessaire :

{
"status": "VALIDATION_ERROR",
"error": "Unknown measure 'Made Up Measure'",
"field": "measures[].name",
"available": ["Unit Sales", "Store Cost", "Store Sales", "Profit", "Customer Count"]
}

L’agent lit field (ce qui était incorrect), lit available[] (les valeurs légales), corrige et réessaie. Deux couches s’exécutent : un validateur de forme (JSON Schema — champs manquants, types incorrects, violations d’enum, avec des chemins de champ indexés par tableau comme filters[0].op) et un validateur sémantique (résolution de cube — noms valides en forme mais qui n’existent pas). Le contrat est identique dans les deux cas : lire field, lire available[], corriger, réessayer.

Où aller ensuite

  • API AI Ask — la couche en langage naturel qui produit le corps de requête ci-dessus, plus les endpoints compagnons members/search, scenario/whatif et pii-suggestions.
  • Agent Spaces — des personas qui limitent cette surface à un ensemble de cubes en liste blanche.
  • Agent Skills — des workflows créés par l’admin, découvrables depuis chaque ask.
  • Serveur MCP — la même surface typée pour les hôtes d’agents externes (list_cubes, describe_cube, run_query, …).
  • API AI Query pour les modèles Ossie — le pendant SQL/YAML-sémantique.
  • API d’inférence AI — une surface différente : le designer de cube au moment de la conception (profile → propose → render), pas l’exécution de requête.