Pular para o conteúdo

AI Query API — cubos OLAP

A AI Query API permite que um agente consulte um cubo OLAP Mondrian sem nunca ver ou escrever MDX. O agente busca um schema autodescritivo, preenche uma requisição JSON contra ele, o servidor valida cada nome, executa e retorna registros tipados. Se um nome está errado, o erro diz ao agente exatamente o que consertar — sem prompt engineering necessário.

Esta é a contraparte OLAP da AI Query API para modelos Ossie (a superfície de SQL/YAML semântico). Mesma disciplina, mesmas proteções; esta trabalha contra cubos Mondrian em vez de YAML Ossie. Assentada sobre ela está a camada de linguagem natural /ai/ask — o endpoint ask traduz uma pergunta em inglês simples exatamente para o corpo de requisição documentado aqui.

Orientação rápida

Três endpoints cobrem ~90% do uso por agentes:

EndpointPropósito
GET /rest/saiku/api/ai/cubesLista todo cubo que o chamador pode consultar.
GET /rest/saiku/api/ai/schema/{cubeId}Schema autodescritivo — measures, dimensions, hierarchies, levels, sample members, synonyms, e o JSON Schema da requisição.
POST /rest/saiku/api/ai/queryExecuta uma requisição tipada. Resposta em formato records por default; matrix em ?format=matrix.

Endpoints de cauda longa (documentados nas páginas linkadas):

EndpointPropósito
GET /rest/saiku/api/ai/members/searchLookup por substring de members em um level.
POST /rest/saiku/api/ai/scenario/whatifSimulação de what-if com write-back.
POST /rest/saiku/api/ai/query/execute-asyncSubmete para execução em background (consulte status / result).
POST /rest/saiku/api/ai/anomalyExecuta a query e então sinaliza anomalias ao longo de um eixo temporal.
POST /rest/saiku/api/ai/forecastProjeta pontos futuros (ETS / ARIMA / Prophet).
POST /rest/saiku/api/ai/askPergunta em linguagem natural. Precisa de uma chave de LLM.

O cubeId em todos os lugares é o quádruplo connection/catalog/schema/cubeName unido com /. Todos os endpoints exigem uma sessão autenticada; endpoints POST exigem o par cookie/header de CSRF.

Passo 1 — listar os cubos

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

O quádruplo connectionName/catalog/schema/cubeName é o identificador de cubo usado em todos os outros lugares.

Passo 2 — buscar o schema tipado

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

Não faça URL-encode das barras — o template do path aceita a forma multi-segmento connection/catalog/schema/cubeName diretamente. A resposta é densa — é isso que torna a API autodescritiva:

{
"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]" }
]
}
}
}
}
}
}
}

Synonyms e aliases de nome de exibição são aceitos como input em qualquer lugar onde o name canônico é — um agente pode dizer "revenue" e o servidor o resolve para Store Sales.

Passo 3 — executar uma query

“Show Store Sales and Unit Sales by Product Family, top 3 by 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 aceita tanto a forma de objeto de 4 segmentos quanto a string compacta "connection/catalog/schema/cube".

Resposta (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
}

Cada linha é um objeto autodescritivo indexado pelo caption legível da coluna. Toda célula numérica é um envelope tipado:

  • value — número parseado (para matemática / ordenação / gráficos)
  • formatted — a string de exibição pré-formatada do Mondrian (para UI)
  • unit — farejada da string formatada (USD, GBP, EUR, JPY, %) ou null

generatedMdx é ecoado para debug; agentes tipicamente o ignoram.

Formato matrix

Clientes indexados por posição optam por sair de records com ?format=matrix — a resposta carrega matrix em vez de data, cada linha indexada pelo índice da coluna como string, células ainda no envelope tipado {value, formatted, unit}.

Privacidade: k-anonimato

Quando ai.kAnonymity está definido (default 5; 0 desabilita), o servidor mascara valores de measure de células pequenas antes que o resultado cruze a fronteira da AI — qualquer linha cuja measure de contagem no resultado cai abaixo de k tem suas células de measure mascaradas com suppressed: true. Aplica-se a records e matrix, e aos endpoints derivados /ai/anomaly + /ai/forecast.

Passo 4 — validação ensina o agente

Forneça um nome que não resolve e o servidor retorna 400 com um corpo do qual o agente pode se autocorrigir — sem prompting de retry necessário:

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

O agente lê field (o que estava errado), lê available[] (os valores legais), conserta e tenta de novo. Duas camadas rodam: um validador de formato (JSON Schema — fields faltando, tipos errados, violações de enum, com paths de field indexados por array como filters[0].op) e um validador semântico (resolução do cubo — nomes que são válidos de formato mas não existem). O contrato é idêntico de qualquer forma: leia field, leia available[], conserte, tente de novo.

Para onde ir a seguir

  • AI Ask API — a camada de linguagem natural que produz o corpo de requisição acima, mais os endpoints companheiros members/search, scenario/whatif e pii-suggestions.
  • Agent Spaces — personas que escopam esta superfície a um conjunto de cubos em allowlist.
  • Agent Skills — workflows autorados por admin, descobríveis de cada ask.
  • Servidor MCP — a mesma superfície tipada para hosts de agentes externos (list_cubes, describe_cube, run_query, …).
  • AI Query API para modelos Ossie — a contraparte em SQL/YAML semântico.
  • AI inference API — uma superfície diferente: o designer de cubos em design-time (profile → propose → render), não execução de query.