Saltearse al contenido

API de inferencia de IA

La API de inferencia de IA impulsa el Diseñador de schemas del dashboard. Expone el mismo flujo de tres pasos — profile, propose, render — como endpoints que puede llamar desde su propio código. Útil cuando quiere automatizar la generación de cubos a través de muchos data warehouses, incrustar la creación de cubos en su propio producto, o integrar Saiku Cloud en un flujo de onboarding ascendente.

Todos los endpoints requieren una API key Bearer. Consulte Autenticación para lo básico.

El flujo

El Diseñador de schemas del dashboard es la implementación de referencia canónica:

  1. Perfilar una conexión o un archivo subido. Muestreamos su estructura de forma económica y devolvemos un SchemaProfile.
  2. Proponer un cubo. Enviamos el perfil + una intención opcional en lenguaje natural a Claude, y devolvemos un CubeProposal estructurado.
  3. Renderizar la propuesta como XML de schema de Mondrian. El resultado es una cadena lista para guardarse como schema en su workspace.

Puede llamar a los pasos de forma independiente — perfilar una vez y proponer varias veces con diferentes intenciones, u omitir el paso de proponer y construir a mano un CubeProposal para el renderizador.

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

Perfila una conexión de data warehouse guardada. Lee information_schema, muestrea unas filas por columna, devuelve un perfil estructurado de las tablas y columnas del data warehouse.

Parámetros de ruta:

Parámetros de query opcionales:

  • schema — restringir a un schema de base de datos específico (por ejemplo public, analytics). Por defecto usa el schema con el que se guardó la conexión.
  • maxTables — limitar el número de tablas perfiladas. Por defecto 20.
  • tableTypes — lista separada por comas (TABLE, VIEW, MATERIALIZED VIEW). Por defecto solo TABLE.

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

Coste: normalmente menos de 0,05 USD por perfil contra un data warehouse de Postgres. La mayor parte del coste son consultas de metadatos, no escaneos de datos.

Modos de fallo:

  • 404 not_found — el ID de conexión no existe o no es visible para su tenant.
  • 502 warehouse_unreachable — no pudimos conectar con el data warehouse (credenciales incorrectas, host caído, problema de red).

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

Obtiene unas filas de muestra de una tabla. Misma autenticación y forma de accesibilidad que el endpoint de perfil, alcance más reducido.

Parámetros de ruta + query:

  • id — ID de conexión.
  • table — nombre de la tabla (requerido).
  • schema — schema de la base de datos (por defecto el de la conexión).
  • rows — número de filas (por defecto 5, máximo 50).

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

Útil para mostrar al usuario cómo se ven realmente sus datos antes de comprometerse con un diseño de cubo.

POST /me/inference/profile/file

Perfila un archivo subido en lugar de una tabla de data warehouse. Misma forma que el perfilador de conexión, con el ID del archivo sustituyendo al ID de conexión.

Cuerpo multipart:

  • file — la parte del archivo. .parquet, .csv o .json.
  • tableTypes — por defecto TABLE (misma forma que el perfilador de conexión, permite expansión futura).

Respuesta (200): misma forma SchemaProfile que el perfilador de conexión. Los archivos aparecen como una única entrada tables[0].

El httpfs de DuckDB lee el archivo columna a columna, por lo que un archivo Parquet de varios GB se perfila en segundos sin que tengamos que traer la cosa entera a memoria.

POST /me/inference/propose

Envía un perfil a Claude y recibe una propuesta de cubo.

Cuerpo de la petición:

{
"profile": { /* SchemaProfile from the profile step */ },
"intent": "Sales facts joined to customer and product dimensions, sum of revenue, count of orders.",
"factTable": { "schema": "public", "name": "fact_sales" }
}
  • profile (requerido) — el JSON devuelto por cualquiera de los endpoints de perfil.
  • intent (opcional pero muy recomendado) — descripción en lenguaje natural de una línea de lo que quiere. Sin esto, Claude tiene mucho menos con lo que trabajar.
  • factTable (opcional) — fijar una tabla de hechos específica. Sin esto, Claude elige una basándose en heurísticas de nombres y formas de columna.

Respuesta (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": { /* condensed proposal — measures + dim names + join graph */ },
"detectorFindings": { /* heuristic insights about date hierarchies, etc */ },
"usage": {
"model": "claude-sonnet-4-6",
"inputTokens": 4321,
"outputTokens": 892,
"totalTokens": 5213,
"estimatedCostUsd": 0.018,
"isPricingExact": true
},
"quota": { /* monthly LLM budget snapshot */ }
}

Algo clave a tener en cuenta: la respuesta ya incluye el mondrianXml renderizado para la ruta feliz. No necesita llamar a /render por separado a menos que haya editado primero la propuesta.

El traceId le permite recuperar más adelante la conversación LLM completa mediante GET /me/inference/trace/{traceId} — útil para auditoría o depuración de propuestas inesperadas.

Modos de fallo:

  • 400 invalid_proposal — Claude devolvió una propuesta estructuralmente inválida que nuestro validador rechazó. Aun así se devuelve el trace ID.
  • 429 over_budget — su tenant ha superado su presupuesto LLM mensual. Viene con un encabezado Retry-After y un campo reset_at que muestra cuándo se restablece el presupuesto.
  • 502 upstream_error — Claude devolvió un error de red o rechazó la petición.

POST /me/inference/render

Convierte un SchemaProposal en XML de schema de Mondrian.

Cuerpo de la petición — el JSON de la propuesta directamente (no envuelto en {proposal: …}). Use el campo proposal de una respuesta previa de /propose, con cualquier edición local aplicada:

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

Respuesta (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": { /* condensed view — measures + dim names + join graph */ }
}

Transformación pura — sin llamada LLM, sin interacción con el data warehouse. Gratis.

Puede llamar a render varias veces sobre la misma propuesta mientras la edita — eso es exactamente lo que hace el Diseñador de schemas en el bucle de edición del dashboard.

Modos de fallo:

  • 400 invalid_proposal — la propuesta no satisface las restricciones del XML de Mondrian (falta un campo requerido, joins contradictorios, etc). Cuerpo de la respuesta: { error, kind, message }.
  • 400 empty_proposal — el cuerpo de la petición estaba vacío.

POST /me/inference/try-query

Ejecuta una consulta MDX de muestra contra un cubo en borrador antes de guardarlo. Útil para confirmar que los joins caen donde espera.

Cuerpo de la petición:

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

Respuesta (200):

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

El cubo se compila en memoria; no se guarda nada. Use esto en su propio bucle de iteración igual que hace el Diseñador de schemas del dashboard.

GET /me/inference/trace/{traceId}

Recupera la conversación LLM de una llamada anterior a propose. Cada respuesta de propose incluye un traceId; pásela aquí para obtener el prompt + respuesta completos.

Respuesta (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..."
}

Útil para:

  • Depurar propuestas inesperadas. Vea exactamente qué enviamos a Claude y qué nos devolvió.
  • Pista de auditoría. Los equipos de cumplimiento a veces quieren un registro de lo que generó la IA para revisión humana.
  • Iteración. Compare dos propuestas consecutivas para ver qué hizo Claude de forma diferente.

Los traces se conservan durante 30 días, luego se purgan. Con RLS limitado a su tenant — solo puede recuperar sus propios traces.

Ejemplo de principio a fin

Un script completo — perfilar un data warehouse, proponer un cubo, renderizar el XML, guardarlo:

Ventana de terminal
KEY="$SAIKU_API_KEY"
CONN_ID="abc-123-def"
# 1. Profile (extract the `profile` field — propose wants the
# profile object, not the whole response).
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: the propose response already includes mondrianXml.
echo "$PROPOSAL" | jq -r '.mondrianXml' > sales-cube.xml
# 3. Re-render (only needed if you've edited the proposal).
# Important: the render endpoint takes the proposal JSON
# directly — NOT wrapped in {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. Save (via /me/schemas — not covered on this page yet)
echo "$XML" > sales-cube.xml

Reemplace el paso 4 con lo que su flujo de trabajo necesite — guardar en su propio repo, hacer commit en git, entregar a un revisor humano.

Próximos pasos