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.

Last updated: 2026-07-21

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

BenefitWithout PromptsWith Prompts
Update LLM instructionsModify plugin code, rebuild, redeployUpdate the prompt via API — takes effect immediately
Share across pluginsCopy-paste prompt strings between pluginsBoth plugins reference the same prompt_id
A/B testingDeploy two plugin versionsRegister two prompts, switch the ID in the plugin config
RollbackRedeploy the previous plugin versionRe-upload the previous template via API
Audit trailCheck git history for prompt changesThe 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

text
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".
💡Tip

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:

ScopeVisibilityWho Can EditUse Case
userOnly the creating userThe creating user onlyPersonal experimentation and testing
tenantAll users in the tenant (organization)Tenant adminsShared across a team — most common (default)
globalAll users on the platformPlatform administrators onlySystem-wide defaults provided by bizSupply
ℹ️Note

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:

FieldTypeDescription
purposestring (max 50)Categorizes the prompt's role (e.g., classification, extraction). Prompts with the same purpose are treated as variants.
model_affinitystring (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:

  1. You register multiple prompts with the same purpose but different model_affinity values.
  2. In your plugin config, you reference a single base prompt ID.
  3. At runtime, the platform detects which LLM model is active (e.g., gemini-3-flash-preview → tier gemini_3).
  4. The platform automatically resolves the model-specific variant.
  5. The plugin receives the resolved prompt transparently — no code changes needed.

Example — two classification prompts for different models:

PromptPurposeModel AffinityDescription
Classification v13classificationgemini_3Optimized for the Gemini 3.x thinking model
Classification v8classificationgemini_2Optimized 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 ContainsResolved Tier
gemini-3gemini_3
gemini-2gemini_2
gptopenai
claudeclaude

Step 3 — Register via the API

Register the prompt by uploading the template file as multipart form data. Metadata is supplied as query parameters:

bash
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.txt

Using curl (universal prompt):

bash
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):

bash
="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:

json
{
  "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:

bash
GET /prompts
Authorization: Bearer <your-jwt-token>
json
{
  "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.

python
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 None

When running a pipeline, pass the prompt ID as a plugin config value:

json
{
  "plugin_id": "your-plugin-id",
  "plugin_configs": {
    "classification_prompt_id": "550e8400-e29b-41d4-a716-446655440000"
  }
}
ℹ️Note

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

bash
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.

bash
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.txt

Delete a Prompt

bash
DELETE /prompts/{prompt_id}
Authorization: Bearer <your-jwt-token>
⚠️Warning

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

text
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

text
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

text
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