AI Ask API
Die AI Ask API nimmt eine Frage in einfachem Englisch entgegen, fordert den konfigurierten LLM-Provider auf, eine typisierte Abfrage gegen das aktive Cube-Schema auszufüllen, führt diese Abfrage durch denselben Konverter, den die typisierte AI Query API verwendet, und gibt das ausgeführte Ergebnis + die generierte MDX + die strukturierte Anfrage zurück, die das Modell erzeugt hat.
Es ist die Natural-Language-Schicht, die über der typisierten Agenten-Oberfläche
sitzt. Während die typisierte API voraussetzt, dass der Aufrufer
AiQueryRequest spricht, erlaubt die Ask-API einem Menschen, „Zeige
Umsatz nach Land im letzten Quartal” zu sagen und ein vollständig
ausgeführtes Cellset zurückzubekommen, mit der Übersetzung des Modells
sichtbar daneben.
Dies treibt das AI Query Drawer im Workspace an — die ⚡-Schaltfläche in der Symbolleiste öffnet ein Chat-Panel, in dem Benutzer Fragen eingeben und auf In Canvas bearbeiten klicken, um das Ergebnis in den aktiven Abfrage-Tab zu übernehmen.
Wann ist sie zu verwenden?
Die Ask-API ist die richtige Oberfläche, wenn:
- Endbenutzer Fragen tippen — die typisierte AI Query API erwartet einen Agenten, der das Cube-Schema bereits kennt; die Ask-API verbirgt diesen Schritt hinter einem LLM.
- Sie einen Round-Trip pro Frage wollen —
/ai/askerledigt Übersetzung + Ausführung + Envelope-Shaping in einem einzigen Aufruf, sodass der Client nicht zwei APIs verketten muss. - Sie eine Chat-Oberfläche einbetten — das mehrstufige
history-Feld ermöglicht es, Folgefragen gegen frühere Runden aufzulösen.
Wann sie nicht zu verwenden ist:
- Agenten am MCP Server — Agenten bevorzugen die typisierte Oberfläche, weil sie bereits gut darin sind, strukturierte Felder aus einem JSON Schema auszufüllen. Die Ask-Schicht ist für Menschen.
- Cubes aus einem Sample-Warehouse erstellen — verwenden Sie die AI Inference API, die für den Design-Time-Flow konzipiert ist.
Aktivierung
Zwei Properties + eine Env-Variable pro Provider. Beide können in der JVM oder in der Properties-Datei des Deployments gesetzt werden, die von Spring konsumiert wird.
# Anthropic Claudesaiku.ai.ask.provider = anthropic# env ANTHROPIC_API_KEY = sk-ant-...
# OpenAI — oder jeder OpenAI-kompatible Host (Azure, vLLM, Ollama, Together)saiku.ai.ask.provider = openai# env OPENAI_API_KEY = sk-...
# Optional, beide Providersaiku.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-... # explizites Override der Env-VariableProvider-Standardwerte:
| Provider | Standard-Modell | Endpunkt |
|---|---|---|
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
Verwenden Sie ein Secret für den Key und eine ConfigMap für den
Provider-Namen:
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=anthropicDer Endpunkt
POST /saiku/api/ai/askAnfrage
{ "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 ist optional. Wenn angegeben, werden frühere (user, assistant)-Runden
an das Modell zurückgesendet, sodass Folgefragen wie „jetzt nach Region
aufschlüsseln” gegen die frühere Frage aufgelöst werden. System-Prompts
werden vom Provider kontrolliert — Aufrufer können sie nicht einschleusen.
Antwort
{ "degraded": false, "model": "claude-sonnet-4-6", "request": { /* der strukturierte AiQueryRequest, den das Modell erzeugt hat */ }, "response": { /* die vollständige AiQueryResponse — gleiche Form wie /ai/query */ }, "generatedMdx": "SELECT NON EMPTY ... FROM [Sales]"}request ist die strukturierte Abfrage, die das Modell erzeugt hat.
Übergeben Sie sie wortwörtlich an POST /ai/query,
um sie erneut auszuführen, oder zeigen Sie sie dem Benutzer als „die
typisierte Abfrage hinter Ihrer Frage” an.
response ist das ausgeführte Ergebnis, gleiche Form wie die typisierte
AI Query API zurückgibt. Wenn das Modell eine Anfrage erzeugt hat, die
das Schema ablehnt, ist response.status gleich VALIDATION_ERROR und
der Body enthält field + available-Kandidaten — das Workspace-Drawer
rendert diese als anklickbare Chips, damit der Benutzer selbst korrigieren
kann.
generatedMdx ist ein bequemer Spiegel von
response.metadata.generatedMdx.
Statuscodes
| HTTP | Wann |
|---|---|
200 | Übersetzung erfolgreich; response enthält das ausgeführte Ergebnis (oder VALIDATION_ERROR zur Selbstkorrektur). |
200 + degraded:true | Übersetzung in der Provider-Schicht fehlgeschlagen — Transportfehler, Modell hat abgelehnt oder Parse-Fehler. reason enthält die Erklärung. |
400 | Fehlendes question oder cube im Body. |
503 + degraded:true | Provider ist noop (nicht konfiguriert) — reason erklärt, wie aktiviert wird. |
Beispiele
Standardmäßig aus — klare Rückmeldung
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..."# }Happy Path — vollständiger Round-Trip
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# }Folgefrage — Mehrstufig
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."} ]}EOFDas Modell sieht die vorherige Runde und löst „nach Quartal” so auf, dass es auch gegen Land gekreuzt wird — gleiche Form wie die erste Antwort mit einer zusätzlichen Hierarchie.
Was das Modell sieht vs. nicht sieht
Die Ask-Schicht sendet dem Modell nur:
- Das
AiSchemades aktiven Cubes — serialisiertes JSON der Measures, Dimensionen, Hierarchien, Levels und Sample-Member. - Das JSON Schema von
AiQueryRequestalsinput_schemades Structured-Output-Tools. - Die Frage des Benutzers und etwaige Konversationshistorie, die der Aufrufer angehängt hat.
Das Modell wird über tool_choice gezwungen, das emit_query-Tool mit
einer gültigen AiQueryRequest-Form aufzurufen. Rohe Prosa-Antworten
werden als degraded abgelehnt.
Es sieht niemals:
- Andere Cubes, auf die der Benutzer zugreifen kann — nur den in der Anfrage genannten.
- Das zugrundeliegende SQL oder die Warehouse-Anmeldedaten.
- Konversationshistorien anderer Benutzer.
- Die HTTP-Anfrage, Session-Cookies oder irgendeinen Header, den der Aufrufer gesendet hat.
Dies ist dasselbe Isolationsmodell, das der MCP Server und die typisierte AI Query API verwenden, nur eine Schicht früher angewendet.
Wie geht es weiter?
- AI Inference API — die typisierte Oberfläche,
in die
/ai/askübersetzt. Nützlich, wenn ein Agent (kein Mensch) die Anfrage erstellen soll. - MCP Server — dieselbe typisierte Oberfläche als MCP-Tools für Claude Desktop / Cursor / Cline Agenten.
- Authentifizierung — Bearer-Tokens, Rate-Limits, Fehlerformate.