Create a Prompt
Prompts are reusable LLM instruction templates stored in the platform. Instead of hardcoding prompt strings in your plugin code, you register prompts separately and load them at runtime with self.get_prompt(prompt_id). This lets you update prompts without redeploying plugins, share them across teams, and manage prompt variants independently.
Prompts are reusable LLM instruction templates stored in the platform. Instead of hardcoding prompt strings in your plugin code, you register prompts separately and load them at runtime with self.get_prompt(prompt_id). This lets you update prompts without redeploying plugins, share them across teams, and manage prompt variants independently.
What Prompts Are
A prompt is a named text template — optionally with {{PLACEHOLDER}} markers — that plugins load at runtime by prompt_id. Prompts are stored in the platform and are not executable on their own; they are inputs to the prompt_llm() service method.
- Centralized — all prompt logic lives in one place, separate from plugin code.
- Reusable — the same prompt can be referenced by many plugins.
- Scoped — prompts can be user-level, tenant-level, or global, controlling who can access and modify them.
- Testable — update and test prompts without rebuilding or redeploying plugins.
Benefits
| Benefit | Without Prompts | With Prompts |
|---|---|---|
| Update LLM instructions | Modify plugin code, rebuild, redeploy | Update the prompt via API — takes effect immediately |
| Share across plugins | Copy-paste prompt strings between plugins | Both plugins reference the same prompt_id |
| A/B testing | Deploy two plugin versions | Register two prompts, switch the ID in the plugin config |
| Rollback | Redeploy the previous plugin version | Re-upload the previous template via API |
| Audit trail | Check git history for prompt changes | The platform tracks who changed what and when |
Step 1 — Write the Template
Write your prompt as a text file. Use clear, structured instructions, and {{PLACEHOLDER}} markers for any dynamic content your plugin substitutes at runtime.
Example: invoice_classification.txt
You are a document classification expert.
Analyze the following document and classify it as exactly one of:
{{CATEGORIES}}
Document filename: {{FILENAME}}
Rules:
1. Respond with ONLY the document type label.
2. Do not include any explanation or punctuation.
3. If the document does not match any category, respond with "unknown".Use descriptive placeholder names that make the template self-documenting. Prefer {{DOCUMENT_CONTENT}}, {{FIELD_DEFINITIONS}}, {{CATEGORIES}} over generic names like {{INPUT}} or {{DATA}}.
Step 2 — Choose a Scope
Prompts have three scope levels that control visibility and access:
| Scope | Visibility | Who Can Edit | Use Case |
|---|---|---|---|
user | Only the creating user | The creating user only | Personal experimentation and testing |
tenant | All users in the tenant (organization) | Tenant admins | Shared across a team — most common (default) |
global | All users on the platform | Platform administrators only | System-wide defaults provided by bizSupply |
When a plugin resolves a prompt, the platform resolves scope in order: user → tenant → global. A user-scoped prompt with the same purpose as a tenant-scoped prompt takes precedence for that user.
Step 2b — Set Purpose and Model Affinity (Optional)
Prompts support model-aware versioning — you can create multiple variants of the same prompt, each optimized for a different LLM model, and the platform automatically selects the right one at runtime.
Fields:
| Field | Type | Description |
|---|---|---|
purpose | string (max 50) | Categorizes the prompt's role (e.g., classification, extraction). Prompts with the same purpose are treated as variants. |
model_affinity | string (max 30) | The LLM model tier this prompt is optimized for (e.g., gemini_3, gemini_2, openai, claude). Null = universal. |
How model-aware resolution works:
- You register multiple prompts with the same
purposebut differentmodel_affinityvalues. - In your plugin config, you reference a single base prompt ID.
- At runtime, the platform detects which LLM model is active (e.g.,
gemini-3-flash-preview→ tiergemini_3). - The platform automatically resolves the model-specific variant.
- The plugin receives the resolved prompt transparently — no code changes needed.
Example — two classification prompts for different models:
| Prompt | Purpose | Model Affinity | Description |
|---|---|---|---|
| Classification v13 | classification | gemini_3 | Optimized for the Gemini 3.x thinking model |
| Classification v8 | classification | gemini_2 | Optimized for Gemini 2.5 Flash |
The plugin references v13 as the base prompt. If the active model is Gemini 2.5, the platform swaps to v8 automatically.
Model tier mapping:
| Model Name Contains | Resolved Tier |
|---|---|
gemini-3 | gemini_3 |
gemini-2 | gemini_2 |
gpt | openai |
claude | claude |
Step 3 — Register via the API
Register the prompt by uploading the template file as multipart form data. Metadata is supplied as query parameters:
POST /prompts
Authorization: Bearer <your-jwt-token>
Content-Type: multipart/form-data
Query parameters:
- name: "Invoice Classification" (required)
- description: "Classifies documents as invoices" (optional)
- scope: "tenant" (optional, default: "tenant")
- purpose: "classification" (optional, for model-aware resolution)
- model_affinity: "gemini_3" (optional, null = universal)
Form fields:
- prompt_file: invoice_classification.txtUsing curl (universal prompt):
curl -X POST "https://api.bizsupply.ai/prompts?name=Invoice%20Classification&description=Classifies%20documents%20as%20invoices&scope=tenant" \
-H "Authorization: Bearer YOUR_JWT_TOKEN" \
-F "prompt_file=@invoice_classification.txt"Using curl (model-specific variants):
="color:#5c6370;font-style:italic"># Gemini 3 variant
curl -X POST "https://api.bizsupply.ai/prompts?name=Classification%20(Gemini%203)&purpose=classification&model_affinity=gemini_3&scope=global" \
-H "Authorization: Bearer YOUR_JWT_TOKEN" \
-F "prompt_file=@classification_gemini3.txt"
="color:#5c6370;font-style:italic"># Gemini 2 variant (same purpose, different affinity)
curl -X POST "https://api.bizsupply.ai/prompts?name=Classification%20(Gemini%202)&purpose=classification&model_affinity=gemini_2&scope=global" \
-H "Authorization: Bearer YOUR_JWT_TOKEN" \
-F "prompt_file=@classification_gemini2.txt"Response:
{
"prompt_id": "550e8400-e29b-41d4-a716-446655440000"
}Save the prompt_id — you'll need it when configuring plugins.
Step 4 — Verify
Confirm the prompt was registered:
GET /prompts
Authorization: Bearer <your-jwt-token>{
"count": 1,
"prompts": [
{
"prompt_id": "550e8400-e29b-41d4-a716-446655440000",
"name": "Invoice Classification",
"description": "Classifies documents as invoices",
"scope": "tenant",
"purpose": "classification",
"model_affinity": "gemini_3",
"created_at": "2025-01-12T10: 00: 00Z",
"updated_at": "2025-01-12T10: 00: 00Z"
}
]
}Step 5 — Use in Plugins
Reference the prompt by ID through a configurable_parameter, then load it at runtime with await self.get_prompt(prompt_id) and substitute any placeholders before calling the LLM.
from bizsupply_sdk import ClassificationPlugin, ConfigurableParameter
class FinancialClassifier(ClassificationPlugin):
"""Classifies documents using a platform-managed prompt."""
configurable_parameters = [
ConfigurableParameter(
parameter_name="classification_prompt_id",
parameter_type="str",
default_value=None,
description="Prompt ID for classification instructions",
),
]
async def classify(
self, document, file_data, mime_type, available_labels, current_path, configs
) -> str | None:
# Resolve the prompt ID from the runtime configuration
prompt_id = configs.get("classification_prompt_id")
# Load the template (model-affinity variant resolved automatically)
template = await self.get_prompt(prompt_id)
# Substitute placeholders with runtime values
prompt = template.replace(
"{{CATEGORIES}}",
"\n".join(f"- {label}" for label in available_labels),
).replace(
"{{FILENAME}}",
document.filename,
)
# The LLM reads the attached file bytes directly
result = await self.prompt_llm(
prompt=prompt,
file_data=file_data,
mime_type=mime_type,
)
if not isinstance(result, dict):
return None
label = result.get("label")
return label if label in available_labels else NoneWhen running a pipeline, pass the prompt ID as a plugin config value:
{
"plugin_id": "your-plugin-id",
"plugin_configs": {
"classification_prompt_id": "550e8400-e29b-41d4-a716-446655440000"
}
}prompt_llm(...) returns the parsed response (dict | list | None); it accepts file_data, mime_type, an optional schema, and an optional model_name. If a prompt ID doesn't resolve, get_prompt() raises an error — register the prompt before deploying the plugin.
Management Operations
Get a Prompt
GET /prompts/{prompt_id}
Authorization: Bearer <your-jwt-token>Update a Prompt
Update metadata and/or the template file. Pass null for a field to clear it; omit a field to leave it unchanged.
PATCH /prompts/{prompt_id}
Authorization: Bearer <your-jwt-token>
Content-Type: multipart/form-data
Query parameters (all optional):
- name: "New Name"
- description: "New description"
- purpose: "classification" (use null to clear)
- model_affinity: "gemini_3" (use null to clear, omit to keep unchanged)
Form fields (optional):
- prompt_file: updated_prompt.txtDelete a Prompt
DELETE /prompts/{prompt_id}
Authorization: Bearer <your-jwt-token>Deleting a prompt causes any plugin that references its prompt_id to fail when it calls get_prompt(). Make sure no active plugin references the prompt before deleting it.
Common Patterns
Classification Prompt
You are a document classification expert.
Analyze the following document and classify it as exactly one of:
{{CATEGORIES}}
Document filename: {{FILENAME}}
Respond with ONLY a JSON object: {"label": "...", "confidence": 0.0-1.0}.Extraction Prompt
You are a document extraction expert.
Extract the following fields from this {{DOCUMENT_TYPE}} document:
{{FIELDS}}
Rules:
1. Return ONLY a JSON object with the field names as keys.
2. For each field, include a {field_name}_confidence key with a value from 0.0 to 1.0.
3. If a field cannot be found, set its value to null.
4. For date fields, use ISO 8601 format (YYYY-MM-DD).
5. For number fields, return raw numbers without currency symbols or commas.Contract Analysis Prompt
You are a contract analysis expert specializing in {{CONTRACT_TYPE}} agreements.
Analyze the following contract and extract:
{{FIELDS}}
Pay special attention to:
- Renewal clauses and auto-renewal terms
- Termination conditions and notice periods
- Pricing escalation clauses
- Liability caps and indemnification
Return a JSON object with the extracted fields, plus a "risk_flags" array listing any concerning clauses.Next Steps
- Create a plugin: follow Create a Plugin to build a plugin that uses your prompt.
- Run a pipeline: follow Create a Pipeline to execute prompt-powered plugins.
- Create an ontology: follow Create an Ontology for structured extraction schemas.