Pular para o conteúdo

Agent Spaces

Agent Spaces são personas nomeadas, autoradas por admin, que escopam um ask de AI. Onde Agent Skills codificam workflows individuais, um space codifica um ponto de vista: system prompt, allowlist de cubos, allowlist de skills, prompts sugeridos — aplicados server-side para que, não importa o que o chamador envie, o LLM veja a voz da persona + os cubos da persona + as skills da persona.

Persistido como JSON sob saiku-home/agent-spaces/. O launcher escaneia preguiçosamente por assinatura de mtime (mesmo modelo das skills — sem thread watcher).

Distribuído no saiku v4.7 como saiku#1440.

O que um space aplica

Formato de arquivo

saiku-home/agent-spaces/foodmart-sales-analyst.json
{
"id": "foodmart-sales-analyst",
"name": "FoodMart Sales Analyst",
"description": "Weekly and monthly sales rollups over the FoodMart Sales cube.",
"systemPrompt": "You are the FoodMart Sales Analyst. Prefer weekly and monthly time grain unless the user asks otherwise. Lead with the top three lines by absolute value. Flag any figure that swings by more than 20% versus the prior period. Be analytical, brief, and numbers-first.",
"cubeAllowlist": [
{"connectionName": "unknown_foodmart", "catalog": "FoodMart", "schema": "FoodMart", "cubeName": "Sales"}
],
"skillAllowlist": ["weekly-foodmart-rollup"],
"suggestedPrompts": [
"How did Store Sales track last week vs the prior week?",
"Break down Store Sales by Product Family for Q4.",
"/weekly-foodmart-rollup"
]
}
CampoObrigatórioTipoNotas
idsimstringkebab-case, [a-z][a-z0-9-]{0,63}. Segmento de path em /ai/spaces/{id}/ask.
namesimstringNome de exibição para o catálogo e a sidebar.
descriptionnãostringResumo de uma linha mostrado no seletor de space.
systemPromptnãostringPrependado ao SYSTEM_PROMPT embutido em cada ask.
cubeAllowlistsimAiCubeRef[]Ao menos uma entrada. Refs fora desta lista retornam 403 FORBIDDEN.
skillAllowlistnãostring[]Filtra o slash-routing + o catálogo do LLM. Vazio = todas as skills permitidas.
suggestedPromptsnãostring[]Perguntas de quick-start livres que a UI pode renderizar.

Chaves top-level desconhecidas são rejeitadas para que um typo (sytemPrompt) apareça como UNKNOWN_FIELD em vez de ser silenciosamente descartado.

Enforcement de cubo

  1. O chamador faz POST em /ai/spaces/{id}/ask com um field cube opcional:

    Terminal window
    curl -sS -X POST -H 'Content-Type: application/json' \
    -u admin:admin \
    http://localhost:8080/saiku/api/ai/spaces/foodmart-sales-analyst/ask \
    -d '{"question": "How did Store Sales track last week?"}'
  2. Se cube é omitido, a primeira entrada em allowlist do space é usada.

  3. Se cube é fornecido, ele deve casar com uma entrada da allowlist do space em todas as quatro coordenadas (connectionName, catalog, schema, cubeName) ou a chamada retorna:

    HTTP 403 Forbidden
    {
    "degraded": true,
    "reason": "FORBIDDEN: cube OtherCube is not in space 'foodmart-sales-analyst' allowlist"
    }

Isso é deliberado: uma UI que entrega ao servidor uma cube ref velha deve ser corrigida, não silenciosamente reinterpretada para o default.

Injeção de system prompt

O systemPrompt do space é prependado ao SYSTEM_PROMPT embutido no lado do provider. A mensagem de sistema completa montada que o LLM vê:

You are a Mondrian OLAP analyst assistant scoped to a single cube. …
[…the built-in tool-choice rails…]
Agent space persona:
You are the FoodMart Sales Analyst. Prefer weekly and monthly time
grain unless the user asks otherwise. Lead with the top three lines by
absolute value. Flag any figure that swings by more than 20% versus the
prior period. Be analytical, brief, and numbers-first.
Cube schema:
{ … the AiSchema JSON … }
Cube ref to echo: { … }

Os fields history e question do usuário vêm abaixo de tudo isso como mensagens comuns — nada que eles possam injetar reescreve a persona.

Filtro de skills

O catálogo de skills é filtrado para a skillAllowlist do space antes de alcançar o LLM:

  • A skillAllowlist está vazia → todas as skills fluem.
  • A skillAllowlist nomeia skills específicas → só essas aparecem no system prompt do LLM E só essas fazem slash-expand.

Um ask como /some-other-skill for Q4 em um space que não faz allowlist de some-other-skill cai como um ask cru — o LLM vê a mensagem literalmente, sem expansão. A decisão de roteamento é unilateral (deny da allowlist), nunca parcial.

Prompts sugeridos

Todo space carrega uma lista suggestedPrompts — 3-6 perguntas de quick-start autoradas que a UI expõe como chips:

"suggestedPrompts": [
"How did Store Sales track last week vs the prior week?",
"Break down Store Sales by Product Family for Q4.",
"Which Product Department is up the most month-over-month?",
"/weekly-foodmart-rollup"
]

Entradas de slash-command são legais e encorajadas — um prompt sugerido que começa com / invoca a skill nomeada diretamente.

Superfície REST

GET /rest/saiku/api/ai/spaces

Catálogo de resumos {id, name, description, suggestedPrompts}.

Terminal window
curl -sS -u admin:admin \
http://localhost:8080/saiku/api/ai/spaces | jq
{
"spaces": [
{
"id": "foodmart-sales-analyst",
"name": "FoodMart Sales Analyst",
"description": "Weekly and monthly sales rollups over the FoodMart Sales cube.",
"suggestedPrompts": ["How did Store Sales track last week?", ""]
}
]
}

GET /rest/saiku/api/ai/spaces?errors=true

Mesmo, mais um array errors[]. Códigos de erro estáveis:

CódigoQuando
EMPTY_SPACEO arquivo está vazio ou só whitespace.
MALFORMED_JSONO parser JSON rejeitou o corpo.
MISSING_FIELDCampo obrigatório (id, name) não presente.
BLANK_FIELDCampo obrigatório presente mas vazio / só-whitespace.
TYPE_MISMATCHCampo presente mas tipo errado.
INVALID_IDid não casa com [a-z][a-z0-9-]{0,63}.
EMPTY_ALLOWLISTcubeAllowlist presente mas vazio — o space seria inutilizável.
INVALID_CUBE_REFEntrada da allowlist faltando uma coordenada (connectionName, etc.).
UNKNOWN_FIELDFrontmatter contém um campo que não está no schema.
DUPLICATE_IDDois arquivos declararam o mesmo id.
IO_ERRORNão foi possível ler o arquivo (nível de filesystem).

GET /rest/saiku/api/ai/spaces/{id}

Registro completo — inclui systemPrompt e cubeAllowlist que o resumo omite. Usado pela UI de admin ao editar uma persona.

POST /rest/saiku/api/ai/spaces/{id}/ask

Ask escopado ao space. O formato do corpo espelha /ai/ask mas o field cube é opcional:

Terminal window
curl -sS -X POST -H 'Content-Type: application/json' \
-u admin:admin \
http://localhost:8080/saiku/api/ai/spaces/foodmart-sales-analyst/ask \
-d '{"question": "Break down Store Sales by Product Family for Q4"}'

O envelope de resposta é o padrão AiAskApi.AskResponsemodel, request (o AiQueryRequest que o modelo emitiu), e degraded/reason em erro.

POST /rest/saiku/api/ai/spaces/{id}/ask/stream

Variante de streaming do ask escopado ao space. Emite o mesmo schema de Server-Sent Events que /ai/ask/stream (modelintentchunkfinal) com o escopo da persona aplicado — o cliente vê eventos de wire idênticos quer bata em /ai/ask/stream quer neste espelho escopado ao space. Resultados de space-não-encontrado e cubo fora-da-allowlist aparecem como um evento error seguido por um final degradado, para que o leitor trate falhas de escopo e falhas de provider da mesma forma.

Terminal window
curl -sS -N -X POST -H 'Content-Type: application/json' \
-u admin:admin \
http://localhost:8080/saiku/api/ai/spaces/foodmart-sales-analyst/ask/stream \
-d '{"question": "Break down Store Sales by Product Family for Q4"}'

POST /rest/saiku/api/ai/spaces/refresh

Força um rescan (ignora a checagem de assinatura de mtime).

Autorando spaces no painel de admin

Spaces não precisam mais ser JSON autorado à mão. Admin → Agent spaces é um editor completo: escreva o system prompt, marque os cubos na allowlist (respaldado por descoberta de cubos ao vivo, então você só pode permitir cubos que existem), liste as skills e prompts sugeridos e salve. Deletar um space remove seu arquivo JSON.

O editor é respaldado por uma superfície CRUD admin-gated. Esses endpoints exigem ROLE_ADMIN e retornam a persona completa (system prompt + cube allowlist incluídos, ao contrário do catálogo público redigido acima):

Método + pathPropósito
GET /rest/saiku/admin/agent-spacesLista cada persona por completo, para edição.
GET /rest/saiku/admin/agent-spaces/errorsErros de parse para arquivos malformados.
PUT /rest/saiku/admin/agent-spaces/{id}Cria ou substitui uma persona. O id é validado (kebab-case, protegido contra path-traversal) antes de o arquivo JSON ser escrito.
DELETE /rest/saiku/admin/agent-spaces/{id}Remove uma persona e seu arquivo.

Embutindo um space

<saiku-embed kind="ai" space="foodmart-sales-analyst"> insere um assistente escopado à persona em qualquer página. A allowlist de cubos e o system prompt são aplicados server-side exatamente como são para o ask REST, então um assistente embutido não pode ser desviado da sua persona. Veja o guia de embed.

Exemplos incluídos

Instalações Saiku novas preparam duas personas funcionais:

  • FoodMart Sales Analyst — analítico, breve, numbers-first. weekly-foodmart-rollup na sua skill allowlist para que /weekly-foodmart-rollup esteja disponível como slash command.
  • FoodMart Finance Ops — cauteloso, preciso, focado em margem. skillAllowlist vazia = todas as skills permitidas.

Ambos escopam ao cubo FoodMart Sales — uma demo nova tem personas prontas para clicar sem nenhuma autoria de operador.

Não-objetivos para v1

  • Escopo por-usuário ou por-role. Spaces são por-launcher em v1; workspaces multi-tenant podem camadar escopo mapeando diretórios de workspace para raízes de registro por-workspace — adiado.
  • Sobrescrita de data-scope por-space. Queries embutidas já aplicam filtros row-level forçados (apply-or-fail-closed — veja o guia de embed), mas fixar filtros de RLS em asks escopados a space especificamente ainda é rastreado com o trabalho de RLS Ossie em saiku#1393.

Para onde ir a seguir