Skip to content

Use AI Studio from your plugin, the CLI, the SDK and MCP ​

What you're doing ​

AI Studio is where a workspace builds its AI: model profiles, prompts, knowledge bases, RAG pipelines, agents and evaluations. Everything in it is a plain definition with a code, versions and labels, so a plugin can ship it, and any code can call it.

This guide shows the four ways to use it from a plugin-based application:

  1. Ship AI assets inside your plugin, so every tenant that installs it gets them.
  2. Call them from plugin workflows and services.
  3. Drive it from the erp CLI and your AI coding agent (MCP).
  4. Call it from any code with an OpenAI-compatible SDK and an app API key.

The model is never named in your plugin. You refer to a model profile such as default, and each tenant decides which real model, key and fallback sit behind it.

1. Ship AI assets in your plugin ​

Put one JSON file per asset under spk-assembly/metadata/:

FolderWhat it isSchema name
prompt/<code>.jsona prompt with {{variables}}ai-prompt
rag/<code>.jsona RAG pipeline (how to answer from knowledge)ai-rag-pipeline
eval/<code>.jsontest questions and scorersai-eval-dataset
knowledge/<code>.jsondocuments to searchai-knowledge-base
agent/<code>.jsonan agent (tools, knowledge, guardrails)ai-agent

A prompt, metadata/prompt/summarise-policy.json:

json
{
  "promptCode": "summarise-policy",
  "name": "Summarise a policy",
  "systemTemplate": "You explain HR policies in plain language.",
  "userTemplate": "Summarise this policy in three bullet points:\n\n{{document}}",
  "profileCode": "default",
  "labels": ["prod"]
}

A RAG pipeline, metadata/rag/hr-qa.json, that answers from the plugin's own knowledge base:

json
{
  "pipelineCode": "hr-qa",
  "name": "HR policy answers",
  "definition": {
    "retrieve": { "knowledgeBases": ["hr-policies"], "mode": "HYBRID", "topK": 5 },
    "generate": { "profileCode": "default" },
    "noAnswerText": "I could not find this in the HR policies."
  },
  "labels": ["prod"]
}

An agent, metadata/agent/hr-helper.json, that takes its instructions from the prompt library and checks requests for prompt injection:

json
{
  "agentCode": "hr-helper",
  "name": "HR helper",
  "instructions": "Answer HR questions from the policies.",
  "instructionsPrompt": "hr-helper-brief@prod",
  "autonomyLevel": "READ",
  "modelProfile": "default",
  "knowledgeSources": ["hr-policies"],
  "guardrails": { "input": { "enabled": true, "checks": ["injection"], "action": "block" } }
}

What happens on install and on upgrade ​

  • A new prompt or pipeline is created as version 1 with the labels you list (default prod).
  • On an upgrade with changed content a new version is added. A label moves to it only if it still points at a version your plugin installed. If the tenant took the label over (they edited the prompt and moved prod), their choice is never overwritten; the new version is offered as the label plugin-latest instead.
  • Evaluation datasets are created once. Tenants add their own cases.
  • An agent's instructionsPrompt and guardrails apply when the agent is new or has none set yet.
  • One malformed file is skipped and logged. It never stops the rest of the install.

Add them from Plugin Studio, the CLI or MCP ​

  • Plugin Studio: open your plugin, find Prompts, RAG pipelines, Evaluations, AI agents or Knowledge bases in the Explorer and choose + Add. Pick Copy from AI Studio to bring in something you already built and tested, or Start from a template.
  • CLI: erp plugin op add-ai-asset --pluginId hr-assistant --kind prompt --code summarise-policy --source workspace
  • MCP: ask your coding agent to call erp_plugin_op with add-ai-asset (same arguments).

Check the files ​

bash
erp schema validate spk-assembly/metadata/prompt/summarise-policy.json --schema ai-prompt
erp schema validate spk-assembly/metadata/agent/hr-helper.json --schema ai-agent

The CLI checks required fields and allowed values. The install itself also checks codes (lowercase letters, digits and dashes).

2. Call AI from plugin workflows and services ​

In a workflow, add AI steps as service tasks. They read earlier results with a whole-value token such as ${context.<taskKey>.<field>} (letters, digits and underscores only) and return typed fields for later conditions:

json
{
  "taskKey": "analyze",
  "stage": "review",
  "kind": "service",
  "taskType": "ai.classify",
  "payload": { "code": "exit-risk", "label": "prod", "values": { "reason": "${context.get_employee.reason}" } }
}

The task types are ai.prompt, ai.classify (the prompt answers in JSON), ai.retrieve, ai.rag, ai.agent and ai.guardrail. In Studio's Workflow Designer they are in the Add task list under AI. The workflow engine keeps control of the path; the AI step does one bounded thing.

In a Service-mode plugin (Python, Node.js or Go), the scaffold from erp service-plugin:scaffold includes an AI helper. Set ERP_AI_BASE_URL and ERP_AI_KEY, then:

python
from app.ai import run_prompt, ask

summary = run_prompt("summarise-policy", {"document": text})
result = ask("hr-qa", "How many leave days do I get?")   # {"answer": "...", "citations": [...]}
ts
import { runPrompt, ask } from "./ai";

const summary = await runPrompt("summarise-policy", { document: text });
const { answer, citations } = await ask("hr-qa", "How many leave days do I get?");
go
// ai.go in the Go scaffold; the key decides the tenant, so no tenant id is passed
summary, err := RunPrompt(ctx, "summarise-policy", map[string]any{"document": text}, "prod")
result, err := Ask(ctx, "hr-qa", "How many leave days do I get?", "prod") // result.Answer, result.Citations

3. Use it from the CLI and your AI coding agent ​

The same operations table drives erp plugin op <name> and the MCP tool erp_plugin_op, so a coding agent can work with a tenant's AI without any extra setup:

OperationWhat it does
ai-prompts, ai-rag-list, ai-kb-listlist what the tenant has built
ai-prompt-runrun a prompt (code, label, values)
ai-rag-runask a pipeline a question, with citations and per-stage details
ai-kb-querysearch a knowledge base
ai-eval-runrun an evaluation dataset against a prompt or pipeline label
ai-usagecalls, tokens, latency and cost for the last N days
ai-bundle-export, ai-bundle-importmove AI assets between tenants as one JSON bundle
ai-keys, ai-key-createmanage app API keys

The five schemas above are also served by erp_list_schemas and erp_get_schema, so an agent can write a valid prompt, pipeline or agent file the first time.

4. Call it from any code ​

Make an app API key (AI Studio, or erp plugin op ai-key-create --name "Billing app"). The secret starts with sk-spark-, is shown once, and decides the tenant. You can limit a key to certain models and a request rate. Then use any OpenAI SDK:

python
from openai import OpenAI

client = OpenAI(base_url="https://your-erp/api/v1/ai/openai/v1", api_key="sk-spark-...")
reply = client.chat.completions.create(model="default", messages=[{"role": "user", "content": "Say hello"}])

model can be a profile code (default), a prompt (prompt:summarise-policy@prod, with the variables as JSON in the last message) or a RAG pipeline (rag:hr-qa@prod, the question in the last message; citations come back in a top-level citations field). /embeddings and /models work too.

How to verify it worked ​

  1. Install the plugin on a tenant, then open AI Studio. Your prompt, pipeline and agent are listed, with the prod label.
  2. In Prompts, run your prompt in the playground. In RAG pipelines, run a question and read what each stage did.
  3. Check Models > Usage. Your calls appear by source, for example prompt:summarise-policy.

Common mistakes ​

  • Naming a model in the plugin. Use a profile code. A vendor model name breaks on tenants that use another provider.
  • Editing a shipped prompt in AI Studio and expecting an upgrade to replace it. By design a tenant's own prod choice wins; look for plugin-latest.
  • A pipeline that names a knowledge base the plugin does not ship. Ship it in metadata/knowledge/, or make sure the tenant has it.
  • Sending a tenant id with an API key. The key already decides the tenant, and any tenant header is ignored.