API AI Ask
L’API AI Ask prend une question en langage naturel, demande au fournisseur LLM configuré de remplir une requête typée à partir du schema de cube actif, exécute cette requête via le même convertisseur que l’API AI Query typée, et renvoie le résultat exécuté + le MDX généré + la requête structurée émise par le modèle.
C’est la couche en langage naturel posée sur la surface d’agent
typée. Là où l’API typée suppose que l’appelant parle
AiQueryRequest, l’API Ask permet à un humain de dire « montre
les ventes par pays au dernier trimestre » et de recevoir en
retour un cellset entièrement exécuté avec la traduction du
modèle visible à côté.
C’est ce qui alimente le panneau AI Query de l’espace de travail — le bouton ⚡ de la barre d’outils ouvre un panneau de chat où les utilisateurs saisissent des questions et cliquent sur Edit in canvas pour déposer le résultat dans l’onglet de requête actif.
Quand l’utiliser
L’API Ask est la bonne surface lorsque :
- Des utilisateurs finaux saisissent des questions — l’API AI Query typée attend un agent qui connaît déjà le schema du cube ; l’API Ask masque cette étape derrière un LLM.
- Vous voulez un aller-retour par question —
/ai/askeffectue la traduction + l’exécution + la mise en forme de l’enveloppe en un seul appel, pour que le client n’ait pas à chaîner deux API. - Vous intégrez une surface de type chat — le champ
multi-tour
historypermet aux questions de suivi de se résoudre par rapport aux tours précédents.
Quand ne pas l’utiliser :
- Agents sur le serveur MCP — les agents préfèrent la surface typée car ils savent déjà bien remplir les champs structurés à partir d’un JSON Schema. La couche Ask est destinée aux humains.
- Création de cubes à partir d’un entrepôt d’exemple — utilisez l’API d’inférence IA, conçue pour le flux à la conception.
Activation
Deux propriétés + une variable d’environnement par fournisseur. Les deux peuvent être définies sur la JVM ou dans le fichier de propriétés du déploiement consommé par Spring.
# Anthropic Claudesaiku.ai.ask.provider = anthropic# env ANTHROPIC_API_KEY = sk-ant-...
# OpenAI — or any OpenAI-compatible host (Azure, vLLM, Ollama, Together)saiku.ai.ask.provider = openai# env OPENAI_API_KEY = sk-...
# Optional, both providerssaiku.ai.ask.model = claude-sonnet-4-7 | gpt-4o-mini | ...saiku.ai.ask.endpoint = https://my.openai-compatible.host/v1/chat/completionssaiku.ai.ask.apiKey = sk-... # explicit override of the env varValeurs par défaut des fournisseurs :
| Fournisseur | Modèle par défaut | Endpoint |
|---|---|---|
anthropic | claude-sonnet-4-6 | https://api.anthropic.com/v1/messages |
openai | gpt-4o-mini | https://api.openai.com/v1/chat/completions |
Docker
docker run -d --name saiku \ -e ANTHROPIC_API_KEY=sk-ant-... \ -e JAVA_OPTS='-Dsaiku.ai.ask.provider=anthropic' \ ghcr.io/spiculedata/saiku:latestKubernetes
Utilisez un Secret pour la clé, un ConfigMap pour le nom du
fournisseur :
apiVersion: v1kind: Secretmetadata: name: saiku-aitype: OpaquestringData: ANTHROPIC_API_KEY: sk-ant-...---apiVersion: apps/v1kind: Deploymentmetadata: name: saikuspec: template: spec: containers: - name: saiku envFrom: - secretRef: name: saiku-ai env: - name: JAVA_OPTS value: -Dsaiku.ai.ask.provider=anthropicL’endpoint
POST /saiku/api/ai/askRequête
{ "question": "show sales by country last quarter", "cube": { "connectionName": "foodmart", "catalog": "FoodMart", "schema": "FoodMart", "cubeName": "Sales" }, "history": [ { "role": "user", "content": "earlier question" }, { "role": "assistant", "content": "earlier summary" } ]}history est facultatif. Lorsqu’il est fourni, les tours
précédents (user, assistant) sont renvoyés au modèle afin que
les suivis comme « maintenant ventile par région » se résolvent
par rapport à la question antérieure. Les prompts système sont
contrôlés par le fournisseur — les appelants ne peuvent pas en
injecter.
Réponse
{ "degraded": false, "model": "claude-sonnet-4-6", "request": { /* the structured AiQueryRequest the model emitted */ }, "response": { /* the full AiQueryResponse — same shape as /ai/query */ }, "generatedMdx": "SELECT NON EMPTY ... FROM [Sales]"}request est la requête structurée produite par le modèle.
Transmettez-la telle quelle à POST /ai/query
pour la ré-exécuter, ou exposez-la à l’utilisateur sous la forme
« la requête typée derrière votre question ».
response est le résultat exécuté, avec la même forme que ce
que renvoie l’API AI Query typée. Si le modèle a émis une
requête que le schema rejette, response.status vaut
VALIDATION_ERROR et le corps porte field + les candidats
available — le panneau de l’espace de travail les affiche sous
forme de puces cliquables pour que l’utilisateur puisse
s’auto-corriger.
generatedMdx est un miroir pratique de
response.metadata.generatedMdx.
Codes de statut
| HTTP | Quand |
|---|---|
200 | Traduction réussie ; response porte le résultat exécuté (ou VALIDATION_ERROR pour que l’utilisateur s’auto-corrige). |
200 + degraded:true | La traduction a échoué au niveau du fournisseur — erreur de transport, refus du modèle ou erreur d’analyse. reason porte l’explication. |
400 | question ou cube manquant dans le corps. |
503 + degraded:true | Le fournisseur est noop (non configuré) — reason explique comment l’activer. |
Exemples
Désactivé par défaut — retour clair
curl -sS -X POST -H 'Content-Type: application/json' \ -u admin:admin \ http://localhost:8080/saiku/api/ai/ask \ -d '{"question":"show sales by country","cube":{"connectionName":"foodmart","catalog":"FoodMart","schema":"FoodMart","cubeName":"Sales"}}'
# {# "degraded": true,# "reason": "AI ask is not configured. Set saiku.ai.ask.provider..."# }Cas nominal — aller-retour complet
curl -sS -X POST -H 'Content-Type: application/json' \ -u admin:admin \ http://localhost:8080/saiku/api/ai/ask \ -d '{"question":"show sales by country","cube":{"connectionName":"foodmart","catalog":"FoodMart","schema":"FoodMart","cubeName":"Sales"}}' \ | jq '{model, generatedMdx, rows: .response.totalRows}'
# {# "model": "claude-sonnet-4-6",# "generatedMdx": "SELECT NON EMPTY {[Measures].[Store Sales]} ON COLUMNS, NON EMPTY {[Customers].[Country].Members} ON ROWS FROM [Sales]",# "rows": 3# }Suivi — multi-tour
curl -sS -X POST -H 'Content-Type: application/json' \ -u admin:admin \ http://localhost:8080/saiku/api/ai/ask \ -d @- <<'EOF'{ "question": "now break it down by quarter", "cube": {"connectionName":"foodmart","catalog":"FoodMart","schema":"FoodMart","cubeName":"Sales"}, "history": [ {"role":"user","content":"show sales by country"}, {"role":"assistant","content":"3 rows returned."} ]}EOFLe modèle voit le tour précédent et interprète « par trimestre » comme également croisé avec le pays — même forme que la première réponse avec une hiérarchie supplémentaire.
Ce que voit le modèle (et ce qu’il ne voit pas)
La couche Ask envoie au modèle uniquement :
- L’
AiSchemadu cube actif — JSON sérialisé des mesures, dimensions, hiérarchies, niveaux et membres d’exemple. - Le JSON Schema d’
AiQueryRequestcommeinput_schemade l’outil de sortie structurée. - La question de l’utilisateur et tout historique de conversation joint par l’appelant.
Le modèle est forcé via tool_choice à appeler l’outil
emit_query avec une forme AiQueryRequest valide. Les
réponses en prose brute sont rejetées comme dégradées.
Il ne voit jamais :
- Les autres cubes auxquels l’utilisateur a accès — uniquement celui nommé dans la requête.
- Le SQL sous-jacent ou les identifiants d’entrepôt.
- L’historique de conversation d’autres utilisateurs.
- La requête HTTP, les cookies de session ou les en-têtes envoyés par l’appelant.
C’est le même modèle d’isolation que le serveur MCP et l’API AI Query typée utilisent, appliqué une couche plus tôt.
Et ensuite
- API d’inférence IA — la surface typée vers
laquelle
/ai/asktraduit. Utile lorsque vous voulez qu’un agent (et non un humain) compose la requête. - Serveur MCP — la même surface typée exposée comme outils MCP pour les agents Claude Desktop / Cursor / Cline.
- Authentification — jetons Bearer, limites de débit, formes d’erreur.