Prompts, variables and A/B

Why managed prompts

  • Edit prompts without redeploying your app.
  • Pin a label (production) and swap which version it points to in one click.
  • Version history with diff + rollback.
  • Inject runtime data from your DB/system via {{variables}}.
  • Run experiments without writing routing code.

Call by slug

{
  "model": "gpt-4o-mini",
  "prompt": {
    "slug": "welcome-email",
    "label": "production",
    "variables": { "name": "Ada" }
  }
}

messages is omitted; the server renders it from the template. If you do send messages, your messages win over the rendered ones — handy for appending the live user turn while still inheriting the system prompt from the registry.

Template syntax

Hello {{name}}, welcome to {{product}}.
RuleDetail
Placeholder{{name}} — must start with letter/underscore, then [A-Za-z0-9_]
Missing variableRenders as empty string
Required variableDeclared in the editor → 400 invalid_prompt when omitted
Max length8 KB per variable by default — a variable's declaration can raise its own cap with max_bytes (up to 256 KB per variable, 1 MB total across all values)
SanitisationASCII control chars (except \n/\t) are stripped

Using your own data

You fetch from your DB/system, then hand the result to odnoga as variables. See the full Supabase Edge Function recipe in Using your own data in prompts.

Quick shape:

const user     = await db.users.findUnique({ where: { id } });
const invoices = await db.invoices.findMany({ where: { userId: id }, take: 5 });

await ai.prompts.chat({
  slug: 'billing-assistant',
  label: 'production',
  variables: {
    tenant_name:     user.tenant.name,
    user_full_name:  user.full_name,
    plan:            user.plan,
    invoices_context: invoices.map(i => `- #${i.number}  $${i.amount}`).join('\n'),
  },
});

Versions, labels, ids

FieldUse
slugStable identifier you call.
labelA movable pointer (production, staging).
version_idA specific immutable version. Banned for browser keys and EUTs.

Lookup order: version_id > label > active A/B > production > latest version.

A/B experiments

In Prompts → Experiments, attach two or more versions of the same slug with a traffic share. odnoga picks a version per request and sticks the user to that version using a hash of x-airouter-end-user. Without an end-user id, stickiness degrades to per-request — fine for testing, bad for production.

Each request records the experiment and the bucket it fell into, and results aggregate in the experiment view.

Errors

HTTPcodeWhen
400invalid_promptRequired variable missing
400variable_too_largeVariable over its cap (8 KB default, or the declared max_bytes)
404prompt_not_foundSlug doesn't exist in this workspace
403version_pin_not_allowedversion_id sent with a browser/EUT key