Przejdź do głównej zawartości

API inferencji AI

API inferencji AI napędza Schema designer w dashboardzie. Wystawia ten sam trzykrokowy przepływ — profile, propose, render — jako endpointy, które możesz wywołać z własnego kodu. Przydatne, gdy chcesz zautomatyzować generowanie kostek na wielu hurtowniach, osadzić autorstwo kostek we własnym produkcie albo zintegrować Saiku Cloud z wyżej położonym przepływem onboardingu.

Wszystkie endpointy wymagają klucza API typu Bearer. Podstawy zobacz w Uwierzytelnianiu.

Przepływ

Schema designer w dashboardzie to kanoniczna implementacja referencyjna:

  1. Profile połączenie lub wgrany plik. Tanio próbkujemy jego strukturę i zwracamy SchemaProfile.
  2. Propose kostkę. Wysyłamy profil + opcjonalną intencję po ludzku do Claude’a i zwracamy strukturalny CubeProposal.
  3. Render propozycję jako XML schemy Mondrian. Wynik to string gotowy do zapisania jako schema w Twoim workspace.

Możesz wywoływać kroki niezależnie — profiluj raz i proponuj kilka razy z różnymi intencjami, albo pomiń krok propose i ręcznie zlep CubeProposal dla renderera.

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

Sprofiluj zapisane połączenie hurtowni. Czyta information_schema, próbkuje kilka wierszy na kolumnę, zwraca strukturalny profil tabel i kolumn hurtowni.

Parametry ścieżki:

Opcjonalne parametry query:

  • schema — ogranicz do konkretnej schemy bazy danych (np. public, analytics). Domyślnie schema, dla której połączenie zostało zapisane.
  • maxTables — ogranicz liczbę profilowanych tabel. Domyślnie 20.
  • tableTypes — lista oddzielona przecinkami (TABLE, VIEW, MATERIALIZED VIEW). Domyślnie tylko TABLE.

Odpowiedź (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
}

Koszt: zazwyczaj poniżej $0.05 na profil względem hurtowni Postgres. Większość kosztu to zapytania o metadane, nie skany danych.

Tryby porażki:

  • 404 not_found — ID połączenia nie istnieje lub nie jest widoczne dla Twojego tenanta.
  • 502 warehouse_unreachable — nie udało się połączyć z hurtownią (złe poświadczenia, host nie działa, problem sieciowy).

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

Pobierz kilka przykładowych wierszy z jednej tabeli. Ten sam kształt auth i dostępności co endpoint profilowania, węższy zakres.

Parametry ścieżki + query:

  • id — ID połączenia.
  • table — nazwa tabeli (wymagane).
  • schema — schema bazy danych (domyślnie domyślna połączenia).
  • rows — liczba wierszy (domyślnie 5, max 50).

Odpowiedź (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"]
]
}

Przydatne, by pokazać użytkownikowi, jak naprawdę wyglądają jego dane, zanim zatwierdzi projekt kostki.

POST /me/inference/profile/file

Sprofiluj wgrany plik zamiast tabeli hurtowni. Ten sam kształt co profiler połączeń, z ID pliku w miejscu ID połączenia.

Treść multipart:

  • file — część plikowa. .parquet, .csv lub .json.
  • tableTypes — domyślnie TABLE (ten sam kształt co profiler połączeń, dopuszcza przyszłe rozszerzenia).

Odpowiedź (200): ten sam kształt SchemaProfile co profiler połączeń. Pliki wynurzają się jako pojedyncza pozycja tables[0].

httpfs DuckDB-a czyta plik kolumna po kolumnie, więc wielogigabajtowy plik Parquet profiluje się w sekundach bez wciągania całości w pamięć.

POST /me/inference/propose

Wyślij profil do Claude’a i dostań propozycję kostki.

Treść żądania:

{
"profile": { /* SchemaProfile z kroku profile */ },
"intent": "Sales facts joined to customer and product dimensions, sum of revenue, count of orders.",
"factTable": { "schema": "public", "name": "fact_sales" }
}
  • profile (wymagane) — JSON zwrócony przez którykolwiek endpoint profilowania.
  • intent (opcjonalne, ale gorąco zalecane) — jednolinijkowy opis po ludzku, czego chcesz. Bez tego Claude ma znacznie mniej, na czym pracować.
  • factTable (opcjonalne) — przypnij konkretną tabelę faktów. Bez tego Claude wybiera jedną na podstawie heurystyk nazewniczych i kształtów kolumn.

Odpowiedź (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": { /* skondensowana propozycja — miary + nazwy wymiarów + graf złączeń */ },
"detectorFindings": { /* heurystyczne wnioski o hierarchiach daty itd. */ },
"usage": {
"model": "claude-sonnet-4-6",
"inputTokens": 4321,
"outputTokens": 892,
"totalTokens": 5213,
"estimatedCostUsd": 0.018,
"isPricingExact": true
},
"quota": { /* migawka miesięcznego budżetu LLM */ }
}

Co warto zauważyć: odpowiedź już zawiera wyrenderowany mondrianXml dla ścieżki szczęśliwej. Nie musisz wywoływać osobno /render, chyba że najpierw zmodyfikowałeś propozycję.

traceId pozwala później pobrać pełną konwersację LLM przez GET /me/inference/trace/{traceId} — przydatne do audytu lub debugowania nieoczekiwanych propozycji.

Tryby porażki:

  • 400 invalid_proposal — Claude zwrócił strukturalnie nieprawidłową propozycję, którą nasz walidator odrzucił. Trace ID i tak zwracane.
  • 429 over_budget — Twój tenant przekroczył miesięczny budżet LLM. Wraca z nagłówkiem Retry-After i polem reset_at pokazującym, kiedy budżet się resetuje.
  • 502 upstream_error — Claude zwrócił błąd sieci lub odmówił żądania.

POST /me/inference/render

Konwertuj SchemaProposal na XML schemy Mondrian.

Treść żądania — JSON propozycji bezpośrednio (nie opakowany w {proposal: …}). Użyj pola proposal z wcześniejszej odpowiedzi /propose, z naniesionymi lokalnymi edycjami:

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

Odpowiedź (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": { /* skondensowany widok — miary + nazwy wymiarów + graf złączeń */ }
}

Czysta transformacja — bez wywołania LLM, bez interakcji z hurtownią. Darmowe.

Render możesz wywoływać wielokrotnie na tej samej propozycji w trakcie jej edycji — dokładnie to robi Schema designer w pętli edycji dashboarda.

Tryby porażki:

  • 400 invalid_proposal — propozycja nie spełnia ograniczeń Mondrian XML (brakujące wymagane pole, sprzeczne złączenia itd.). Treść odpowiedzi: { error, kind, message }.
  • 400 empty_proposal — treść żądania była pusta.

POST /me/inference/try-query

Uruchom przykładowe zapytanie MDX względem szkicu kostki przed jej zapisaniem. Przydatne, by potwierdzić, że złączenia lądują tam, gdzie oczekujesz.

Treść żądania:

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

Odpowiedź (200):

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

Kostka jest kompilowana w pamięci; nic nie jest zapisywane. Używaj tego we własnej pętli iteracji tak, jak robi to Schema designer.

GET /me/inference/trace/{traceId}

Pobierz konwersację LLM dla wcześniejszego wywołania propose. Każda odpowiedź propose zawiera traceId; przekaż go tutaj, by dostać pełny prompt + odpowiedź.

Odpowiedź (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": "...pełny prompt systemowy + użytkownika (często 20+ KB)...",
"responseText": "...pełna odpowiedź LLM, sparsowana i niesparsowana..."
}

Przydatne do:

  • Debugowania nieoczekiwanych propozycji. Zobacz dokładnie, co wysłaliśmy do Claude’a i co wróciło.
  • Ślad audytowy. Zespoły compliance czasem chcą rekordu tego, co AI wygenerowało, do przeglądu przez człowieka.
  • Iteracji. Porównaj dwie kolejne propozycje, by zobaczyć, co Claude zrobił inaczej.

Trace’y są przechowywane przez 30 dni, potem czyszczone. RLS zakresuje je do Twojego tenanta — możesz pobierać tylko swoje trace’y.

Przykład end-to-end

Kompletny skrypt — sprofiluj hurtownię, zaproponuj kostkę, wyrenderuj XML, zapisz:

Okno terminala
KEY="$SAIKU_API_KEY"
CONN_ID="abc-123-def"
# 1. Profile (wyciągnij pole `profile` — propose chce obiektu profilu,
# a nie całej odpowiedzi).
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.\"}")
# Ścieżka szczęśliwa: odpowiedź propose już zawiera mondrianXml.
echo "$PROPOSAL" | jq -r '.mondrianXml' > sales-cube.xml
# 3. Ponowny render (potrzebny tylko jeśli edytowałeś propozycję).
# Ważne: endpoint render przyjmuje JSON propozycji
# bezpośrednio — NIE opakowany w {proposal: ...}.
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. Zapis (przez /me/schemas — nie pokryte jeszcze na tej stronie)
echo "$XML" > sales-cube.xml

Zastąp krok 4 tym, czego potrzebuje Twój workflow — zapisz do własnego repo, wrzuć do gita, podaj do recenzji człowiekowi.

Co dalej

  • Schema designer — UI dashboarda zbudowany na tym API.
  • Uwierzytelnianie — tokeny Bearer, limity szybkości.
  • Serwer MCP — dla agentów LLM, którzy chcą odpytywać Twoje kostki (ta strona jest o ich autorstwie).