Zum Inhalt springen

Agent Spaces

Agent Spaces sind benannte, von Admins verfasste Personas, die eine AI-Anfrage beschränken. Wo Agent Skills einzelne Workflows kodifizieren, kodifiziert ein Space eine Sichtweise: System-Prompt, Cube-Allowlist, Skill-Allowlist, vorgeschlagene Prompts — serverseitig durchgesetzt, sodass das LLM, egal was der Aufrufer sendet, die Persona-Stimme + die Cubes der Persona + die Skills der Persona sieht.

Persistiert als JSON unter saiku-home/agent-spaces/. Der Launcher scannt lazy nach mtime-Signatur (dasselbe Modell wie Skills — kein Watcher-Thread).

Ausgeliefert in saiku v4.7 als saiku#1440.

Was ein Space durchsetzt

Dateiformat

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"
]
}
FeldErforderlichTypAnmerkungen
idjastringkebab-case, [a-z][a-z0-9-]{0,63}. Pfadsegment in /ai/spaces/{id}/ask.
namejastringAnzeigename für den Katalog und die Sidebar.
descriptionneinstringEinzeilige Zusammenfassung, im Space-Picker gezeigt.
systemPromptneinstringBei jeder Anfrage dem eingebauten SYSTEM_PROMPT vorangestellt.
cubeAllowlistjaAiCubeRef[]Mindestens ein Eintrag. Refs außerhalb dieser Liste geben 403 FORBIDDEN zurück.
skillAllowlistneinstring[]Filtert Slash-Routing + LLM-Katalog. Leer = alle Skills erlaubt.
suggestedPromptsneinstring[]Freiform-Quickstart-Fragen, die die UI rendern kann.

Unbekannte Top-Level-Schlüssel werden abgelehnt, sodass ein Tippfehler (sytemPrompt) als UNKNOWN_FIELD auftaucht, statt still verworfen zu werden.

Cube-Durchsetzung

  1. Der Aufrufer POSTet an /ai/spaces/{id}/ask mit einem optionalen cube-Feld:

    Terminal-Fenster
    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. Wird cube weggelassen, wird der erste allowlistete Eintrag des Space verwendet.

  3. Wird cube geliefert, muss es auf allen vier Koordinaten (connectionName, catalog, schema, cubeName) mit einem Space-Allowlist-Eintrag übereinstimmen, sonst gibt der Aufruf zurück:

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

Das ist beabsichtigt: Eine UI, die dem Server eine veraltete Cube-Ref übergibt, sollte korrigiert werden, nicht still auf den Standard umgedeutet.

System-Prompt-Injection

Der systemPrompt des Space wird providerseitig dem eingebauten SYSTEM_PROMPT vorangestellt. Die vollständige zusammengesetzte System-Nachricht, die das LLM sieht:

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: { … }

Die history- und question-Felder des Nutzers reiten unter all dem als reguläre Nachrichten — nichts, was sie einschleusen können, schreibt die Persona um.

Skill-Filter

Der Skill-Katalog wird auf die skillAllowlist des Space gefiltert, bevor er das LLM erreicht:

  • Die skillAllowlist ist leer → alle Skills fließen durch.
  • Die skillAllowlist benennt spezifische Skills → nur diese erscheinen im LLM-System-Prompt UND nur diese expandieren per Slash.

Eine Anfrage wie /some-other-skill for Q4 in einem Space, der some-other-skill nicht allowlistet, fällt als rohe Anfrage durch — das LLM sieht die Nachricht wörtlich, ohne Expansion. Die Routing-Entscheidung ist einseitig (Allowlist-Verweigerung), nie partiell.

Vorgeschlagene Prompts

Jeder Space trägt eine suggestedPrompts-Liste — 3–6 verfasste Quickstart-Fragen, die die UI als Chips sichtbar macht:

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

Slash-Command-Einträge sind zulässig und erwünscht — ein vorgeschlagener Prompt, der mit / beginnt, ruft den benannten Skill direkt auf.

REST-Oberfläche

GET /rest/saiku/api/ai/spaces

Katalog von {id, name, description, suggestedPrompts}-Zusammenfassungen.

Terminal-Fenster
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

Gleiches, plus ein errors[]-Array. Stabile Fehlercodes:

CodeWann
EMPTY_SPACEDatei ist leer oder nur Leerraum.
MALFORMED_JSONJSON-Parser hat den Body abgelehnt.
MISSING_FIELDErforderliches Feld (id, name) nicht vorhanden.
BLANK_FIELDErforderliches Feld vorhanden, aber leer / nur Leerraum.
TYPE_MISMATCHFeld vorhanden, aber falscher Typ.
INVALID_IDid passt nicht zu [a-z][a-z0-9-]{0,63}.
EMPTY_ALLOWLISTcubeAllowlist vorhanden, aber leer — Space wäre unbrauchbar.
INVALID_CUBE_REFAllowlist-Eintrag fehlt eine Koordinate (connectionName, etc.).
UNKNOWN_FIELDFrontmatter enthält ein Feld, das nicht im Schema ist.
DUPLICATE_IDZwei Dateien deklarierten dieselbe id.
IO_ERRORDatei konnte nicht gelesen werden (Dateisystem-Ebene).

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

Vollständiger Datensatz — enthält systemPrompt und cubeAllowlist, die die Zusammenfassung auslässt. Verwendet von der Admin-UI beim Bearbeiten einer Persona.

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

Space-beschränkte Anfrage. Die Body-Form spiegelt /ai/ask, aber das cube-Feld ist optional:

Terminal-Fenster
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"}'

Der Antwort-Umschlag ist der Standard-AiAskApi.AskResponsemodel, request (der vom Modell ausgegebene AiQueryRequest) und degraded/reason bei Fehler.

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

Streaming-Variante der space-beschränkten Anfrage. Gibt dasselbe Server-Sent-Events-Schema aus wie /ai/ask/stream (modelintentchunkfinal) mit angewandtem Persona-Scope — der Client sieht identische Wire-Events, ob er /ai/ask/stream oder diesen space-beschränkten Spiegel trifft. Space-nicht-gefunden- und Cube-außerhalb-der-Allowlist-Ergebnisse tauchen als error-Event gefolgt von einem degraded final auf, sodass der Reader Scope-Fehler und Provider-Fehler gleich behandelt.

Terminal-Fenster
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

Erzwingt einen erneuten Scan (umgeht die mtime-Signaturprüfung).

Spaces im Admin-Panel verfassen

Spaces müssen nicht mehr als handverfasstes JSON vorliegen. Admin → Agent spaces ist ein vollständiger Editor: Schreiben Sie den System-Prompt, haken Sie die Cubes in der Allowlist ab (gestützt auf Live-Cube-Discovery, sodass Sie nur Cubes erlauben können, die existieren), listen Sie die Skills und vorgeschlagenen Prompts auf und speichern. Das Löschen eines Space entfernt seine JSON-Datei.

Der Editor wird von einer admin-geschützten CRUD-Oberfläche gestützt. Diese Endpunkte erfordern ROLE_ADMIN und geben die vollständige Persona zurück (System-Prompt + Cube-Allowlist inklusive, anders als der redigierte öffentliche Katalog oben):

Methode + PfadZweck
GET /rest/saiku/admin/agent-spacesListet jede Persona vollständig auf, zum Bearbeiten.
GET /rest/saiku/admin/agent-spaces/errorsParse-Fehler für fehlerhafte Dateien.
PUT /rest/saiku/admin/agent-spaces/{id}Erstellt oder ersetzt eine Persona. Die id wird validiert (kebab-case, gegen Path-Traversal geschützt), bevor die JSON-Datei geschrieben wird.
DELETE /rest/saiku/admin/agent-spaces/{id}Entfernt eine Persona und ihre Datei.

Einen Space einbetten

<saiku-embed kind="ai" space="foodmart-sales-analyst"> setzt einen persona-beschränkten Assistenten in jede Seite. Die Cube-Allowlist und der System-Prompt werden serverseitig genau so durchgesetzt wie bei der REST-Anfrage, sodass ein eingebetteter Assistent nicht von seiner Persona weggelenkt werden kann. Siehe den Embed-Leitfaden.

Mitgelieferte Beispiele

Frische Saiku-Installationen platzieren zwei funktionierende Personas:

  • FoodMart Sales Analyst — analytisch, knapp, zahlenzuerst. weekly-foodmart-rollup in seiner Skill-Allowlist, sodass /weekly-foodmart-rollup als Slash-Command verfügbar ist.
  • FoodMart Finance Ops — vorsichtig, präzise, margen-fokussiert. Leere skillAllowlist = alle Skills erlaubt.

Beide beschränken sich auf den FoodMart-Sales-Cube — eine frische Demo hat Personas zum Anklicken bereit, ohne jegliches Operator-Verfassen.

Nicht-Ziele für v1

  • Beschränkung pro Nutzer oder pro Rolle. Spaces sind in v1 pro Launcher; Multi-Tenant-Workspaces können Beschränkung schichten, indem sie Workspace-Verzeichnisse auf Registry-Roots pro Workspace mappen — zurückgestellt.
  • Daten-Scope-Override pro Space. Eingebettete Abfragen setzen bereits erzwungene Row-Level-Filter durch (apply-or-fail-closed — siehe den Embed-Leitfaden), aber das Anpinnen von RLS-Filtern speziell auf space-beschränkte Anfragen wird noch mit der Ossie-RLS-Arbeit in saiku#1393 verfolgt.

Wohin als Nächstes