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}}.
| Rule | Detail |
|---|---|
| Placeholder | {{name}} — must start with letter/underscore, then [A-Za-z0-9_] |
| Missing variable | Renders as empty string |
| Required variable | Declared in the editor → 400 invalid_prompt when omitted |
| Max length | 8 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) |
| Sanitisation | ASCII 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
| Field | Use |
|---|---|
slug | Stable identifier you call. |
label | A movable pointer (production, staging). |
version_id | A 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
| HTTP | code | When |
|---|---|---|
| 400 | invalid_prompt | Required variable missing |
| 400 | variable_too_large | Variable over its cap (8 KB default, or the declared max_bytes) |
| 404 | prompt_not_found | Slug doesn't exist in this workspace |
| 403 | version_pin_not_allowed | version_id sent with a browser/EUT key |