Praxis — Prompt Model Framework
You are an expert at building optimized LLM prompt models using Praxis. Praxis uses an agentic optimizer to turn a schema + examples into a high-quality, tested model.config.json.
Workflow: Define → Train (optional) → Build → Run
1. Define a model
Create a model.definition.ts file with a default export using defineModel:
import { z } from 'zod';
import { defineModel } from '@drawcall/praxis';
export default defineModel({
name: 'Text Classifier',
student: 'google/gemini-3-flash-preview',
teacher: 'google/gemini-3.1-pro-preview',
description: 'Classify input text into categories.',
version: '1.0',
input: z.object({
text: z.string().describe('The input text'),
}),
output: z.object({
label: z.enum(['a', 'b', 'c']).describe('Classification label'),
confidence: z.number().describe('Confidence score 0-1'),
}),
examples: [
{ input: { text: '...' }, output: { label: 'a', confidence: 0.95 } },
// ... at least 10 examples with { input, output }
],
metric: ({ modelOutput, exampleOutput }) => {
if (!exampleOutput) return null;
return { accuracy: modelOutput.label === exampleOutput.label ? 1 : 0 };
},
// Training options (all optional)
metricWeights: { accuracy: 0.7, confidence: 0.3 },
split: [0.7, 0.15, 0.15],
targetScore: 0.9,
maxIterations: 5,
maxTestsPerIteration: 100,
});
Definition fields
Required:
student — OpenRouter model ID (e.g. 'google/gemini-3-flash-preview')
input — Zod schema for the input
output — Zod schema for the output
examples — Array of { input, output? } (at least 10 for training). Can also be an async function or a lazy provider.
Optional — model:
name — Human-readable label shown in the web UI
teacher — Stronger model used as the optimizer agent (e.g. 'google/gemini-3.1-pro-preview')
description — Short task description included in the prompt
version — Bump to trigger retraining when examples or metrics change
Optional — metrics:
metric — Scoring function. Return Record<string, number> (e.g. { accuracy: 1 }) or null. Required for training.
metricWeights — Control importance of different metrics (equal weights if omitted). A built-in tokenEfficiency metric (default weight 0.1) penalizes reasoning cost overhead — set metricWeights: { tokenEfficiency: 0 } to disable it.
Optional — training:
split — Train/val/test split as [train, val, test] (default: [0.7, 0.15, 0.15]). Must sum to 1.0.
targetScore — Target combined score on validation set. When set, the optimizer iterates up to maxIterations times, continuing the agent conversation when the val score doesn't meet the target.
maxIterations — Max optimization iterations (default: 5). Only takes effect when targetScore is set.
maxTestsPerIteration — Max example test runs the optimizer agent can use per iteration (default: 100). Each example ID passed to test_examples costs one run. Limits agent steps to 2 × maxTestsPerIteration.
Async examples
Examples can be an async function instead of a plain array. Useful for loading from a database or generating synthetic data:
export default defineModel({
// ...
examples: async () => {
const rows = await fetchFromAPI('/training-data');
return rows.map(r => ({ input: { text: r.text }, output: { label: r.label } }));
},
});
You can also use top-level await for simpler cases:
const data = await loadFromDB();
export default defineModel({
// ...
examples: data.map(r => ({ input: { text: r.text }, output: { label: r.label } })),
});
2. Train (optional)
npx praxis train
Auto-discovers model.definition.ts (or .js) anywhere in the project via glob. Runs an agentic optimizer that diagnoses failures, inspects model reasoning, and writes targeted prompt improvements. Outputs model.config.json next to the definition file.
CLI options:
-d, --definition <path> — definition file (default: auto-discover)
-o, --output <path> — output file (default: model.config.json next to the definition)
-f, --force — skip version/schema guard and force retraining
All training parameters (split, targetScore, maxIterations, metricWeights) are configured in the definition, not the CLI.
Training requires a metric and at least 10 examples. Without training, the model works using a default instruction generated from the schema.
3. Build
import { buildRequest } from '@drawcall/praxis';
import modelDefinition from './model.definition.js';
import modelConfig from './model.config.json'; // optional
const request = buildRequest(modelDefinition, { text: 'Hello' }, modelConfig);
// → { messages, schema, student, metric? }
4. Run
npx praxis run --text "Hello world"
The definition and config are auto-discovered via glob. Use -d <path> to specify a definition explicitly.
5. Use in code
import { generateText } from '@drawcall/praxis';
import modelDefinition from './model.definition.js';
// Without training
const { output, score } = await generateText({
definition: modelDefinition,
input: { text: 'Hello' },
});
// With training
import modelConfig from './model.config.json';
const { output, score } = await generateText({
definition: modelDefinition,
input: { text: 'Hello' },
config: modelConfig,
});
// With AI SDK options (topP, maxRetries, mode, seed, etc.)
const { output } = await generateText({
definition: modelDefinition,
input: { text: 'Hello' },
maxRetries: 3,
});
score is Record<string, number> | null — always a per-metric record.
6. Check
npx praxis check
Verifies the config matches the definition (schema, student, teacher, version, metricWeights). Suggests running npx praxis train to fix mismatches.
Key types
ModelDefinition<I, O> — Returned by defineModel(). Fields: name?, student, version?, teacher?, description?, input, output, examples, metric?, metricWeights?, split?, targetScore?, maxIterations?, maxTestsPerIteration?.
ModelConfig — The trained model.config.json. Optional — everything works without it.
ModelRequest<O> — Returned by buildRequest(). Contains messages (AI SDK ModelMessage[]), schema, student, and metric?.
ModelExample<I, O> — { input: z.infer<Input>, output?: z.infer<Output> }. The output field is optional — omit it when the metric evaluates modelOutput without comparing to expected values.
ModelExamples<I, O> — ModelExample[] | (() => Promise<ModelExample[]>). Examples can be a plain array or an async function.
When to help with Praxis
- User asks to "create a model", "define a prompt model", "train a prompt", or "optimize a prompt"
- User has a classification, extraction, analysis, or generation task with structured I/O
- User wants to improve LLM output quality with training data
How to help
- Ask the user what task they want to solve (classification, extraction, summarization, etc.)
- Create a
model.definition.ts with export default defineModel({...}):
- Set
student to an OpenRouter model ID
- Define
input and output as Zod schemas
- Write 10+ examples with
{ input, output } structure
- Add a
metric function returning Record<string, number> or null
- Optionally set
name, teacher, description, version
- Optionally set
metricWeights, split, targetScore, maxIterations, maxTestsPerIteration
- If the metric returns
null during training, Praxis throws an error — ensure examples have output if the metric compares against it
- Tell them to set
OPENROUTER_KEY in .env then run npx praxis train
- Show them how to use
buildRequest / generateText in code, or npx praxis run from CLI
- Use
npx praxis check to verify the config matches the definition
Requirements
OPENROUTER_KEY must be set in .env
- Node.js >= 18
- At least 10 examples in the definition (for training)
1---2name: praxis3description: Define, train, and run optimized LLM prompt models using Praxis. Use when the user wants to create a model definition, train prompts, or run inference with structured input/output schemas.4---56# Praxis — Prompt Model Framework78You are an expert at building optimized LLM prompt models using Praxis. Praxis uses an agentic optimizer to turn a schema + examples into a high-quality, tested `model.config.json`.910## Workflow: Define → Train (optional) → Build → Run1112### 1. Define a model1314Create a `model.definition.ts` file with a default export using `defineModel`:1516```ts17import { z } from 'zod';18import { defineModel } from '@drawcall/praxis';1920export default defineModel({21 name: 'Text Classifier',22 student: 'google/gemini-3-flash-preview',23 teacher: 'google/gemini-3.1-pro-preview',24 description: 'Classify input text into categories.',25 version: '1.0',2627 input: z.object({28 text: z.string().describe('The input text'),29 }),3031 output: z.object({32 label: z.enum(['a', 'b', 'c']).describe('Classification label'),33 confidence: z.number().describe('Confidence score 0-1'),34 }),3536 examples: [37 { input: { text: '...' }, output: { label: 'a', confidence: 0.95 } },38 // ... at least 10 examples with { input, output }39 ],4041 metric: ({ modelOutput, exampleOutput }) => {42 if (!exampleOutput) return null;43 return { accuracy: modelOutput.label === exampleOutput.label ? 1 : 0 };44 },4546 // Training options (all optional)47 metricWeights: { accuracy: 0.7, confidence: 0.3 },48 split: [0.7, 0.15, 0.15],49 targetScore: 0.9,50 maxIterations: 5,51 maxTestsPerIteration: 100,52});53```5455#### Definition fields5657**Required:**58- `student` — OpenRouter model ID (e.g. `'google/gemini-3-flash-preview'`)59- `input` — Zod schema for the input60- `output` — Zod schema for the output61- `examples` — Array of `{ input, output? }` (at least 10 for training). Can also be an async function or a lazy provider.6263**Optional — model:**64- `name` — Human-readable label shown in the web UI65- `teacher` — Stronger model used as the optimizer agent (e.g. `'google/gemini-3.1-pro-preview'`)66- `description` — Short task description included in the prompt67- `version` — Bump to trigger retraining when examples or metrics change6869**Optional — metrics:**70- `metric` — Scoring function. Return `Record<string, number>` (e.g. `{ accuracy: 1 }`) or `null`. Required for training.71- `metricWeights` — Control importance of different metrics (equal weights if omitted). A built-in `tokenEfficiency` metric (default weight 0.1) penalizes reasoning cost overhead — set `metricWeights: { tokenEfficiency: 0 }` to disable it.7273**Optional — training:**74- `split` — Train/val/test split as `[train, val, test]` (default: `[0.7, 0.15, 0.15]`). Must sum to 1.0.75- `targetScore` — Target combined score on validation set. When set, the optimizer iterates up to `maxIterations` times, continuing the agent conversation when the val score doesn't meet the target.76- `maxIterations` — Max optimization iterations (default: `5`). Only takes effect when `targetScore` is set.77- `maxTestsPerIteration` — Max example test runs the optimizer agent can use per iteration (default: `100`). Each example ID passed to `test_examples` costs one run. Limits agent steps to `2 × maxTestsPerIteration`.7879#### Async examples8081Examples can be an async function instead of a plain array. Useful for loading from a database or generating synthetic data:8283```ts84export default defineModel({85 // ...86 examples: async () => {87 const rows = await fetchFromAPI('/training-data');88 return rows.map(r => ({ input: { text: r.text }, output: { label: r.label } }));89 },90});91```9293You can also use top-level `await` for simpler cases:9495```ts96const data = await loadFromDB();97export default defineModel({98 // ...99 examples: data.map(r => ({ input: { text: r.text }, output: { label: r.label } })),100});101```102103### 2. Train (optional)104105```bash106npx praxis train107```108109Auto-discovers `model.definition.ts` (or `.js`) anywhere in the project via glob. Runs an agentic optimizer that diagnoses failures, inspects model reasoning, and writes targeted prompt improvements. Outputs `model.config.json` next to the definition file.110111CLI options:112- `-d, --definition <path>` — definition file (default: auto-discover)113- `-o, --output <path>` — output file (default: `model.config.json` next to the definition)114- `-f, --force` — skip version/schema guard and force retraining115116All training parameters (split, targetScore, maxIterations, metricWeights) are configured in the definition, not the CLI.117118Training requires a metric and at least 10 examples. Without training, the model works using a default instruction generated from the schema.119120### 3. Build121122```ts123import { buildRequest } from '@drawcall/praxis';124import modelDefinition from './model.definition.js';125import modelConfig from './model.config.json'; // optional126127const request = buildRequest(modelDefinition, { text: 'Hello' }, modelConfig);128// → { messages, schema, student, metric? }129```130131### 4. Run132133```bash134npx praxis run --text "Hello world"135```136137The definition and config are auto-discovered via glob. Use `-d <path>` to specify a definition explicitly.138139### 5. Use in code140141```ts142import { generateText } from '@drawcall/praxis';143import modelDefinition from './model.definition.js';144145// Without training146const { output, score } = await generateText({147 definition: modelDefinition,148 input: { text: 'Hello' },149});150151// With training152import modelConfig from './model.config.json';153const { output, score } = await generateText({154 definition: modelDefinition,155 input: { text: 'Hello' },156 config: modelConfig,157});158159// With AI SDK options (topP, maxRetries, mode, seed, etc.)160const { output } = await generateText({161 definition: modelDefinition,162 input: { text: 'Hello' },163 maxRetries: 3,164});165```166167`score` is `Record<string, number> | null` — always a per-metric record.168169### 6. Check170171```bash172npx praxis check173```174175Verifies the config matches the definition (schema, student, teacher, version, metricWeights). Suggests running `npx praxis train` to fix mismatches.176177## Key types178179- **`ModelDefinition<I, O>`** — Returned by `defineModel()`. Fields: `name?`, `student`, `version?`, `teacher?`, `description?`, `input`, `output`, `examples`, `metric?`, `metricWeights?`, `split?`, `targetScore?`, `maxIterations?`, `maxTestsPerIteration?`.180- **`ModelConfig`** — The trained `model.config.json`. Optional — everything works without it.181- **`ModelRequest<O>`** — Returned by `buildRequest()`. Contains `messages` (AI SDK `ModelMessage[]`), `schema`, `student`, and `metric?`.182- **`ModelExample<I, O>`** — `{ input: z.infer<Input>, output?: z.infer<Output> }`. The `output` field is optional — omit it when the metric evaluates `modelOutput` without comparing to expected values.183- **`ModelExamples<I, O>`** — `ModelExample[] | (() => Promise<ModelExample[]>)`. Examples can be a plain array or an async function.184185## When to help with Praxis186187- User asks to "create a model", "define a prompt model", "train a prompt", or "optimize a prompt"188- User has a classification, extraction, analysis, or generation task with structured I/O189- User wants to improve LLM output quality with training data190191## How to help1921931. Ask the user what task they want to solve (classification, extraction, summarization, etc.)1942. Create a `model.definition.ts` with `export default defineModel({...})`:195 - Set `student` to an OpenRouter model ID196 - Define `input` and `output` as Zod schemas197 - Write 10+ examples with `{ input, output }` structure198 - Add a `metric` function returning `Record<string, number>` or `null`199 - Optionally set `name`, `teacher`, `description`, `version`200 - Optionally set `metricWeights`, `split`, `targetScore`, `maxIterations`, `maxTestsPerIteration`201 - If the metric returns `null` during training, Praxis throws an error — ensure examples have `output` if the metric compares against it2023. Tell them to set `OPENROUTER_KEY` in `.env` then run `npx praxis train`2034. Show them how to use `buildRequest` / `generateText` in code, or `npx praxis run` from CLI2045. Use `npx praxis check` to verify the config matches the definition205206## Requirements207208- `OPENROUTER_KEY` must be set in `.env`209- Node.js >= 18210- At least 10 examples in the definition (for training)