ElementsAI

Prompts

Versioned prompt artifacts on ai.model — out schemas, via recovery, budgets, timeouts, and fx.ask.

A prompt is a versioned artifact minted on a model binding (smart.prompt(...)). It names the ask, optional in / out shapes, a recovery chain, and a cost cap — then Flows call it with fx.ask.

For developers who need typed triage JSON and a fallback model — declare once, ask everywhere.

The one rule

Mint prompts with model.prompt(name, options) — there is no top-level ai.prompt. Input is JSON-serialized (no template / {{var}} API). When out is set, the reply must match or the ask throws.

Smallest Example

Mint a prompt on a model

src/core/ai.ts
import { ai } from "okengine";
import { z } from "zod";

export const smart = ai.model("smart", {
  provider: "openrouter",
  model: "openrouter/free",
  apiKey: process.env.OPENROUTER_API_KEY,
});

export const classifyTicket = smart.prompt("support.classify", {
  version: 1,
  budget: { maxCostPerCall: 0.01 },
  in: z.object({ message: z.string() }),
  out: z.object({
    category: z.enum(["billing", "technical", "feature_request"]),
    urgency: z.enum(["low", "medium", "high"]),
  }),
});

Ask from a Flow

src/flows/support/classify.ts
import { on, flow, http } from "okengine";
import { z } from "zod";
import { classifyTicket } from "@/core/ai";

export const classify = on(
  http.post({
    in: z.object({ message: z.string().min(1) }),
  }),
  flow({
    do: async ({ message }, fx) => {
      return await fx.ask(classifyTicket, { message });
    },
  }),
);

Manifest lists asks: ["support.classify"]. Pin a version at call time with fx.ask("support.classify@1", input).

See structured output

With out set, the runtime appends a schema instruction and validates the JSON reply. Invalid shape → AiSchemaValidationError (code: "AiSchemaInvalid") — no automatic correction retry.

Progressive Patterns

Name + optional version — model is the parent binding:

export const summarize = smart.prompt("docs.summarize", { version: 1 });

await fx.ask(summarize, { text: "…" });

Options

OptionTypeDefaultMeaning
versionnumberPin with fx.ask("name@N")
evalsstringPath for oke eval JSONL
budget{ maxCostPerCall?, maxCostPerRun? }Cost contracts
viastring[][parent model]Recovery chain of logical names
timeout"30s" | msAsk deadline (not a cost budget)
inSchemaDeclared / Manifest / doctor — not runtime-validated on ask
outSchemaRuntime-validated (Zod / JSON Schema / field shorthand)

How input reaches the model

There is no template string API. The runtime JSON-stringifies the ask input (redacting secrets), and when out is set appends:

Reply with JSON only matching this schema: …

Pass the fields you need as the second argument to fx.ask — do not invent template / {{message}} options.

fx.ask options

OptionTypeDefaultMeaning
viarefsprompt chainOverride recovery for this call
timeoutdurationprompt timeoutPer-call deadline
toolsFlow refsTool loop via fx.call
maxStepsnumber6Cap on tool rounds

ai.embed is a separate declaration for vector pipelines into store.index:

export const kb = ai.embed("kb", {
  model: "smart",
  into: "knowledge",
});

const vector = await fx.embed(smart, "install guide");
const hits = await fx.search(kb, "how to install", { topK: 5 });

fx.embed records embeds (not asks). Missing into or a non-vector index fails at runtime with ai: embed "…" has no into / vector-index errors.

Troubleshooting

Learn more

  • Models — bindings and providers
  • Agents — multi-step fx.run
  • AI — guardrails and PII egress
  • OpenRouter — cloud ask recipe

Next

On this page