Saltearse al contenido

Agent Skills

Agent Skills son archivos markdown con frontmatter YAML que viven en saiku-home/skills/. El launcher los escanea de forma perezosa en cada petición e inyecta el catálogo en el system prompt del LLM, de modo que cualquier turno de AI Ask — MDX u Ossie — puede enrutar a un flujo de trabajo escrito por un admin en lugar de ir por libre.

Funcionalidad pequeña, valor desproporcionado: permite a los operadores codificar las preguntas canónicas de su equipo (“el rollup ejecutivo de este trimestre”, “la cohorte de churn”, “el informe de comparación de tiendas”) sin ser dueños de otra herramienta más. La skill vive en el repo, se versiona con el modelo semántico, y recibe revisión de código como todo lo demás.

Entregado en saiku v4.7 como saiku#1426.

Dos rutas de invocación

Formato de archivo

saiku-home/skills/weekly-foodmart-rollup.md
---
name: weekly-foodmart-rollup
description: |
Weekly revenue rollup for the FoodMart Sales cube: total Store Sales
by Product Family for the last 7 days, compared to the prior 7 days.
Flags any family with a >20% swing.
cube: unknown_foodmart/FoodMart/FoodMart/Sales
---
## Steps
1. Query total `[Measures].[Store Sales]` and `[Measures].[Unit Sales]`
broken down by `[Product].[Product Family]` for the last 7 days
(`[Time].[Weekly].[Week].&[latest]`).
2. Query the same shape for the prior 7 days
(`[Time].[Weekly].[Week].&[latest - 1]`).
3. Present the result as a two-column table with a `Δ vs prior` percentage
column derived per family.
4. Highlight any Product Family whose `Δ vs prior` swings by more than 20%
in either direction.

Frontmatter

CampoRequeridoTipoNotas
namestringkebab-case, [a-z][a-z0-9-]{0,63}. Usado como el slug del slash.
descriptionstringUna línea o escalar de bloque. Mostrado en /ai/skills y el prompt del LLM.
cubenostringRef connection/catalog/schema/cubeName. Limita la skill.

Las claves de nivel superior desconocidas son rechazadas. Un error tipográfico (descripton) aflora como un error estructurado UNKNOWN_FIELD — nunca como una skill silenciosa sin nombre.

Cuerpo

Todo lo que sigue a la valla --- de cierre es el cuerpo. Se recomienda markdown pero no es obligatorio; el cuerpo se trata como texto opaco y se pega literalmente en el prompt del LLM ante una coincidencia de slash-command. Manténgalo ceñido — el LLM tiene que leer el archivo entero.

Slash command

  1. El usuario (o el widget DimSum en su nombre) publica una ask que empieza con /:

    Ventana de terminal
    curl -sS -X POST -H 'Content-Type: application/json' \
    -u admin:admin \
    http://localhost:8080/saiku/api/ai/ask \
    -d @- <<'EOF'
    {
    "question": "/weekly-foodmart-rollup for Q4 instead of this week",
    "cube": {
    "connectionName":"unknown_foodmart",
    "catalog":"FoodMart",
    "schema":"FoodMart",
    "cubeName":"Sales"
    }
    }
    EOF
  2. El servicio parsea el slash y busca weekly-foodmart-rollup en el catálogo.

  3. Coincidencia → la ask enviada al LLM es:

    Skill: weekly-foodmart-rollup
    ## Steps
    1. Query total [Measures].[Store Sales] …
    User follow-up: for Q4 instead of this week
  4. El LLM ejecuta los pasos, aplica el seguimiento (“Q4 instead of this week”) al filtro de tiempo, y emite un AiQueryRequest como de costumbre.

  5. Fallo → la ask viaja sin cambios. El LLM la ve como un prompt ordinario con un slash inicial. Nada se rompe.

Lenguaje natural

El catálogo aterriza en el system prompt del LLM como:

Available skills (admin-authored workflows). When the user's question
closely matches one of these — or when the message starts with
`/<skill-name>` — use the skill's steps to structure your response
instead of freewheeling:
- /weekly-foodmart-rollup: Weekly revenue rollup for the FoodMart Sales cube…
- /store-comp-report: Store-level comp vs same period last year…
- /churn-cohort: 30/60/90 day churn cohort split by acquisition channel…

El modelo enruta por sí mismo. Si el usuario pregunta “how did stores compare to last year?”, el emparejador de lenguaje natural elige /store-comp-report; el usuario nunca ve la decisión de enrutamiento.

Superficie REST

Los tres endpoints se sitúan bajo la ruta base estándar de AI Ask.

GET /rest/saiku/api/ai/skills

El catálogo — resúmenes compactos {name, description, cube}.

Ventana de terminal
curl -sS -u admin:admin \
http://localhost:8080/saiku/api/ai/skills | jq
{
"skills": [
{
"name": "weekly-foodmart-rollup",
"description": "Weekly revenue rollup for the FoodMart Sales cube: total Store Sales …",
"cube": "unknown_foodmart/FoodMart/FoodMart/Sales"
}
]
}

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

La misma forma, más un array errors[] listando cada archivo que falló al parsear en este escaneo. Cada entrada lleva un código de máquina estable para que los operadores arreglen frontmatter malo sin leer los logs del servidor.

{
"skills": [ /* … */ ],
"errors": [
{
"path": "broken.md",
"code": "MISSING_FRONTMATTER",
"message": "expected leading `---` YAML frontmatter block"
}
]
}

Códigos de error estables:

CódigoCuándo
EMPTY_SKILLEl archivo está vacío o es todo espacios en blanco.
MISSING_FRONTMATTERNo hay valla --- inicial.
EMPTY_BODYEl frontmatter parseó pero no hay markdown tras la valla de cierre.
MALFORMED_YAMLEl parser YAML rechazó el frontmatter.
MISSING_FIELDCampo requerido (name / description) no presente.
BLANK_FIELDCampo requerido presente pero vacío / solo espacios en blanco.
TYPE_MISMATCHCampo presente pero de tipo incorrecto (name: 42).
INVALID_NAMEname no coincide con [a-z][a-z0-9-]{0,63}.
UNKNOWN_FIELDEl frontmatter contiene un campo que no está en el schema.
DUPLICATE_NAMEDos archivos de skill declararon el mismo name.
IO_ERRORNo se pudo leer el archivo (a nivel de sistema de archivos).

GET /rest/saiku/api/ai/skills/{name}

El cuerpo completo de una skill — el markdown crudo. Útil para un slash-menu de UI que previsualiza el flujo de trabajo antes de que el usuario le dé a enviar.

POST /rest/saiku/api/ai/skills/refresh

Fuerza un reescaneo (se salta la comprobación de firma de mtime). Devuelve los recuentos frescos para que los operadores puedan comprobar la recarga a ojo.

Ventana de terminal
curl -sS -u admin:admin -X POST \
http://localhost:8080/saiku/api/ai/skills/refresh
{ "skills": 4, "errors": 0 }

Ejemplo incluido

Las instalaciones nuevas de Saiku preparan un ejemplo funcional en el primer arranque — vea weekly-foodmart-rollup.md. Los operadores añaden las suyas al lado; la sembrada es idempotente (solo aterriza cuando el archivo destino no existe ya).

Cómo funciona el escaneo

  • Los subdirectorios se recorren recursivamente.
  • Los archivos que no son .md se ignoran (un README.txt perdido no hará caer el escaneo).
  • Los archivos rotos no derriban el catálogo — una ParseException en un archivo deja los demás intactos.
  • Los duplicados en name producen un error DUPLICATE_NAME en el segundo archivo en cargarse; el primero gana.

Las asks de Ossie también las recogen

El catálogo de skills es un único almacén por launcher servido en /rest/saiku/api/ai/skills. Cualquier skill cuyo campo cube: nombre una ref Ossie (p. ej. pharma/Pharma/Pharma/Sales) queda naturalmente limitada por esa ref cuando se enruta a través de /ai/ossie/ask. MDX y Ossie comparten la misma primitiva.

A dónde ir después