Zum Inhalt springen

AI Inference API

Die AI Inference API treibt den Schema-Designer im Dashboard an. Sie stellt denselben dreistufigen Flow — profile, propose, render — als Endpunkte zur Verfügung, die Sie aus Ihrem eigenen Code aufrufen können. Nützlich, wenn Sie die Cube-Generierung über viele Warehouses hinweg automatisieren, die Cube-Erstellung in Ihr eigenes Produkt einbetten oder Saiku Cloud in einen vorgelagerten Onboarding-Flow integrieren möchten.

Alle Endpunkte erfordern einen Bearer-API-Key. Siehe Authentifizierung für die Grundlagen.

Der Flow

Der Schema-Designer im Dashboard ist die kanonische Referenzimplementierung:

  1. Profile Sie eine Verbindung oder eine hochgeladene Datei. Wir sampeln ihre Struktur kostengünstig und geben ein SchemaProfile zurück.
  2. Propose Sie einen Cube. Wir senden das Profil + eine optionale einfach-englische Absicht an Claude und geben einen strukturierten CubeProposal zurück.
  3. Render Sie den Vorschlag als Mondrian-Schema-XML. Das Ergebnis ist ein String, der bereit ist, als Schema in Ihrem Workspace gespeichert zu werden.

Sie können die Schritte unabhängig aufrufen — einmal profilen und mehrmals mit unterschiedlichen Absichten vorschlagen oder den Propose-Schritt überspringen und einen CubeProposal von Hand für den Renderer bauen.

POST /me/inference/profile/connection/{id}

Profilieren Sie eine gespeicherte Warehouse-Verbindung. Liest information_schema, sampelt einige Zeilen pro Spalte und gibt ein strukturiertes Profil der Tabellen und Spalten des Warehouses zurück.

Pfadparameter:

Optionale Query-Parameter:

  • schema — auf ein bestimmtes Datenbankschema beschränken (z. B. public, analytics). Standard ist das Schema, gegen das die Verbindung gespeichert wurde.
  • maxTables — Anzahl der profilierten Tabellen begrenzen. Standard 20.
  • tableTypes — kommaseparierte Liste (TABLE, VIEW, MATERIALIZED VIEW). Standard nur TABLE.

Antwort (200):

{
"databaseProductName": "PostgreSQL",
"databaseProductVersion": "16.6",
"tables": [
{
"schema": "public",
"name": "fact_sales",
"rowCount": 1245678,
"columns": [
{
"name": "sale_id",
"type": "BIGINT",
"nullable": false,
"distinctApprox": 1245678,
"sampleValues": [1, 2, 3, 4, 5]
},
{
"name": "customer_id",
"type": "BIGINT",
"nullable": false,
"distinctApprox": 25000,
"sampleValues": [101, 102, 103, 104, 105]
}
]
}
],
"sampledAt": "2026-05-23T18:00:00Z",
"sampleDurationMillis": 1832,
"sampleCostUsd": 0.001
}

Kosten: typischerweise unter $0,05 pro Profil gegen ein Postgres-Warehouse. Die Kosten entfallen hauptsächlich auf Metadaten-Abfragen, nicht auf Daten-Scans.

Fehlermodi:

  • 404 not_found — Connection-ID existiert nicht oder ist für Ihren Tenant nicht sichtbar.
  • 502 warehouse_unreachable — wir konnten das Warehouse nicht erreichen (Anmeldedaten falsch, Host down, Netzwerkproblem).

GET /me/inference/profile/connection/{id}/sample

Holen Sie einige Sample-Zeilen aus einer Tabelle. Gleiche Auth- und Erreichbarkeitsform wie der Profile-Endpunkt, engerer Scope.

Pfad- + Query-Parameter:

  • id — Connection-ID.
  • table — Tabellenname (erforderlich).
  • schema — Datenbankschema (Standard ist der Default der Verbindung).
  • rows — Anzahl der Zeilen (Standard 5, Max. 50).

Antwort (200):

{
"schema": "public",
"table": "fact_sales",
"columns": ["sale_id", "customer_id", "amount", "sale_date"],
"rows": [
[1, 101, "29.99", "2024-01-15"],
[2, 102, "149.00", "2024-01-15"]
]
}

Nützlich, um dem Benutzer zu zeigen, wie seine Daten tatsächlich aussehen, bevor er sich auf ein Cube-Design festlegt.

POST /me/inference/profile/file

Profilieren Sie eine hochgeladene Datei anstelle einer Warehouse-Tabelle. Gleiche Form wie der Connection-Profiler, mit der Datei-ID anstelle der Connection-ID.

Multipart-Body:

  • file — der Datei-Teil. .parquet, .csv oder .json.
  • tableTypes — Standard TABLE (gleiche Form wie der Connection-Profiler, lässt zukünftige Erweiterung zu).

Antwort (200): gleiche SchemaProfile-Form wie der Connection-Profiler. Dateien erscheinen als einzelner tables[0]-Eintrag.

DuckDBs httpfs liest die Datei spaltenweise, sodass eine Multi-GB-Parquet-Datei in Sekunden profiliert wird, ohne dass wir das Ganze in den Speicher ziehen.

POST /me/inference/propose

Senden Sie ein Profil an Claude und erhalten Sie einen Cube-Vorschlag zurück.

Request-Body:

{
"profile": { /* SchemaProfile aus dem Profile-Schritt */ },
"intent": "Sales facts joined to customer and product dimensions, sum of revenue, count of orders.",
"factTable": { "schema": "public", "name": "fact_sales" }
}
  • profile (erforderlich) — das JSON, das einer der Profile-Endpunkte zurückgegeben hat.
  • intent (optional, aber dringend empfohlen) — einzeilige einfach-englische Beschreibung dessen, was Sie wollen. Ohne sie hat Claude viel weniger zu tun.
  • factTable (optional) — eine bestimmte Faktentabelle festlegen. Ohne sie wählt Claude eine basierend auf Namens-Heuristiken und Spalten-Formen aus.

Antwort (200):

{
"traceId": "ac2ab729-7fc3-4c3a-8e36-7cd77be87267",
"proposal": {
"schemaName": "Sales Analytics",
"cubes": [
{
"name": "Sales",
"factTableSchema": "public",
"factTableName": "fact_sales",
"measures": [
{ "name": "Revenue", "column": "amount", "aggregator": "sum" },
{ "name": "Orders", "column": "sale_id", "aggregator": "count" }
],
"dimensions": [
{
"name": "Customer",
"foreignKey": "customer_id",
"tableSchema": "public",
"tableName": "dim_customer",
"primaryKey": "customer_id",
"levels": [
{ "name": "Name", "column": "customer_name", "type": "String", "uniqueMembers": false }
]
}
]
}
]
},
"mondrianXml": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<Schema name=\"Sales Analytics\">\n <Cube name=\"Sales\">...</Cube>\n</Schema>",
"validation": { "valid": true, "cubeCount": 1, "warnings": [] },
"summary": { /* verdichteter Vorschlag — Measures + Dim-Namen + Join-Graph */ },
"detectorFindings": { /* heuristische Einsichten zu Datumshierarchien usw. */ },
"usage": {
"model": "claude-sonnet-4-6",
"inputTokens": 4321,
"outputTokens": 892,
"totalTokens": 5213,
"estimatedCostUsd": 0.018,
"isPricingExact": true
},
"quota": { /* Snapshot des monatlichen LLM-Budgets */ }
}

Wichtig zu beachten: Die Antwort enthält für den Happy Path bereits die gerenderte mondrianXml. Sie müssen /render nicht separat aufrufen, es sei denn, Sie haben den Vorschlag zuvor bearbeitet.

Die traceId erlaubt es Ihnen, die vollständige LLM-Konversation später über GET /me/inference/trace/{traceId} abzurufen — nützlich für Auditing oder zum Debuggen unerwarteter Vorschläge.

Fehlermodi:

  • 400 invalid_proposal — Claude hat einen strukturell ungültigen Vorschlag zurückgegeben, den unser Validator abgelehnt hat. Die Trace-ID wird trotzdem zurückgegeben.
  • 429 over_budget — Ihr Tenant hat sein monatliches LLM-Budget überschritten. Kommt mit einem Retry-After-Header und einem reset_at-Feld, das angibt, wann das Budget zurückgesetzt wird.
  • 502 upstream_error — Claude hat einen Netzwerkfehler zurückgegeben oder die Anfrage abgelehnt.

POST /me/inference/render

Konvertieren Sie einen SchemaProposal in Mondrian-Schema-XML.

Request-Body — das Proposal-JSON direkt (nicht in {proposal: …} eingewickelt). Verwenden Sie das proposal-Feld aus einer vorherigen /propose-Antwort mit allen lokalen Änderungen:

{
"schemaName": "Sales Analytics",
"cubes": [
{
"name": "Sales",
"factTableSchema": "public",
"factTableName": "fact_sales",
"measures": [ /* … */ ],
"dimensions": [ /* … */ ]
}
]
}

Antwort (200):

{
"mondrianXml": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<Schema name=\"Sales Analytics\">\n <Cube name=\"Sales\">...</Cube>\n</Schema>",
"validation": { "valid": true, "cubeCount": 1, "warnings": [] },
"summary": { /* verdichtete Ansicht — Measures + Dim-Namen + Join-Graph */ }
}

Reine Transformation — kein LLM-Aufruf, keine Warehouse-Interaktion. Kostenlos.

Sie können Render mehrmals auf demselben Vorschlag aufrufen, während Sie ihn bearbeiten — genau das tut der Schema-Designer in der Bearbeitungsschleife des Dashboards.

Fehlermodi:

  • 400 invalid_proposal — der Vorschlag erfüllt nicht die Mondrian-XML-Bedingungen (fehlendes Pflichtfeld, widersprüchliche Joins usw.). Antwort-Body: { error, kind, message }.
  • 400 empty_proposal — Request-Body war leer.

POST /me/inference/try-query

Führen Sie eine MDX-Beispielabfrage gegen einen Entwurfs-Cube aus, bevor Sie ihn speichern. Nützlich, um zu bestätigen, dass die Joins dort landen, wo Sie es erwarten.

Request-Body:

{
"proposal": { /* CubeProposal */ },
"connectionId": "uuid-of-saved-connection",
"mdx": "SELECT { [Measures].[Revenue] } ON COLUMNS, { [Customer].[Name].MEMBERS } ON ROWS FROM [Sales]"
}

Antwort (200):

{
"columns": ["Customer", "Revenue"],
"rows": [
["Acme Corp", "1234.56"],
["Beta Inc", "987.65"]
],
"executionMillis": 234
}

Der Cube wird im Speicher kompiliert; nichts wird gespeichert. Verwenden Sie dies in Ihrer eigenen Iterationsschleife so, wie es der Schema-Designer des Dashboards tut.

GET /me/inference/trace/{traceId}

Rufen Sie die LLM-Konversation für einen vorherigen propose-Aufruf ab. Jede Propose-Antwort enthält eine traceId; übergeben Sie sie hier, um den vollständigen Prompt + die Antwort zu erhalten.

Antwort (200):

{
"traceId": "ac2ab729-7fc3-4c3a-8e36-7cd77be87267",
"createdAt": "2026-05-23T18:00:00Z",
"status": "success",
"model": "claude-sonnet-4-6",
"inputTokens": 4321,
"outputTokens": 892,
"estimatedCostUsd": 0.018,
"promptText": "...full system + user prompt (often 20+ KB)...",
"responseText": "...full LLM response, parsed and unparsed..."
}

Nützlich für:

  • Debuggen unerwarteter Vorschläge. Sehen Sie genau, was wir Claude gesendet haben und was zurückkam.
  • Audit-Trail. Compliance-Teams wollen manchmal eine Aufzeichnung dessen, was KI für die menschliche Prüfung generiert hat.
  • Iteration. Vergleichen Sie zwei aufeinanderfolgende Vorschläge, um zu sehen, was Claude anders gemacht hat.

Traces werden 30 Tage aufbewahrt und dann gelöscht. RLS-gescopt auf Ihren Tenant — Sie können nur Ihre eigenen Traces abrufen.

End-to-End-Beispiel

Ein vollständiges Skript — ein Warehouse profilieren, einen Cube vorschlagen, die XML rendern, speichern:

Terminal-Fenster
KEY="$SAIKU_API_KEY"
CONN_ID="abc-123-def"
# 1. Profile (das `profile`-Feld extrahieren — propose will das
# profile-Objekt, nicht die ganze Antwort).
PROFILE=$(curl -sS https://api.saiku.bi/me/inference/profile/connection/$CONN_ID \
-X POST -H "Authorization: Bearer $KEY" -d '{}' \
-H "Content-Type: application/json" | jq '.profile')
# 2. Propose
PROPOSAL=$(curl -sS https://api.saiku.bi/me/inference/propose \
-X POST -H "Authorization: Bearer $KEY" \
-H "Content-Type: application/json" \
-d "{\"profile\": $PROFILE, \"intent\": \"Sales revenue and order count by customer and date.\"}")
# Happy path: die propose-Antwort enthält bereits mondrianXml.
echo "$PROPOSAL" | jq -r '.mondrianXml' > sales-cube.xml
# 3. Re-render (nur erforderlich, wenn Sie den Vorschlag bearbeitet haben).
# Wichtig: Der render-Endpunkt nimmt das proposal-JSON
# direkt — NICHT in {proposal: ...} eingewickelt.
XML=$(echo "$PROPOSAL" | jq '.proposal' | \
curl -sS https://api.saiku.bi/me/inference/render \
-X POST -H "Authorization: Bearer $KEY" \
-H "Content-Type: application/json" \
-d @- | jq -r '.mondrianXml')
# 4. Speichern (über /me/schemas — noch nicht auf dieser Seite behandelt)
echo "$XML" > sales-cube.xml

Ersetzen Sie Schritt 4 durch das, was Ihr Workflow benötigt — in Ihr eigenes Repo speichern, in Git committen, einem menschlichen Reviewer übergeben.

Wie geht es weiter?

  • Schema-Designer — die Dashboard-UI, die auf dieser API aufbaut.
  • Authentifizierung — Bearer-Tokens, Rate-Limits.
  • MCP Server — für LLM-Agenten, die Ihre Cubes abfragen möchten (diese Seite befasst sich mit der Erstellung).