AxGen Codegen Rules (@ax-llm/ax)
This skill helps an LLM generate correct AxGen code using @ax-llm/ax. Use when the user asks about ax(), AxGen, generators, forward(), streamingForward(), validation, assertions, streaming assertions, field processors, step hooks, self-tuning, or structured outputs.
Install
Install only this skill for TypeScript:
npx skills add https://ax-llm.github.io/ax/typescript/ --skill 'ax-gen'Published skill file: ax-gen/SKILL.md.
Source
- Source: src/ax/skills/ax-gen.md
- Version:
22.0.3
Skill Instructions
Use this skill to generate AxGen code. Prefer short, modern, copyable patterns. Do not write tutorial prose unless the user explicitly asks for explanation.
Use These Defaults
- Use
ax(...)factory, notnew AxGen(...). - Always pass an AI instance from
ai(...)as the first argument toforward(). - Streaming uses
streamingForward(), notforward()with a stream option. - Use schema validation for field shape and constraints.
- Use
addAssert(...)for whole-output hard invariants with correction retries. - Use
addStreamingAssert(...)for partial streaming hard invariants with fail-fast per-attempt correction retries. - Use
bestOfN(...)/refine(...)for reward-scored complete outputs. - Step hook mutations are applied at the next step boundary (pending pattern).
stopFunctionaccepts a string or string[] for multiple stop functions.- Multi-step continues until: all outputs filled, stop function called, or
maxStepsreached.
Canonical Pattern
import { ai, ax, s } from '@ax-llm/ax';
const llm = ai({
name: 'openai',
apiKey: process.env.OPENAI_APIKEY!,
});
// Inline signature
const gen = ax('input:string -> output:string, reasoning:string');
// Reusable signature
const sig = s('question:string, context:string[] -> answer:string');
const gen2 = ax(sig);
// With options
const gen3 = ax('input -> output', {
description: 'A helpful assistant',
maxRetries: 3,
maxSteps: 10,
temperature: 0.7,
});
const result = await gen.forward(llm, { input: 'Hello world' });
console.log(result.output);Signatures from zod / valibot / arktype
ax() accepts any signature built with f(), and f().input() / .output() accept Standard Schema v1 validators directly — per-field or a whole z.object({...}):
import { z } from 'zod';
import { ax, f } from '@ax-llm/ax';
const gen = ax(
f()
.input(z.object({
productName: z.string(),
buyerProfile: z.string(),
}))
.output(z.object({
headline: z.string(),
recommendation: z.enum(['buy', 'wait', 'skip']),
}))
.build()
);Constraints (.min(), .email(), .regex()) and custom logic (.refine(), .transform(), .superRefine()) execute in the normal validation/retry pipeline — at parse time on complete field values, including at field boundaries during streaming. For cache/internal hints pass companion options: .input('ctx', z.string(), { cache: true }) or .output('reasoning', z.string(), { internal: true }).
Define tool functions with zod the same way — fn().arg() / .returns() accept per-argument or whole-object schemas and infer the handler’s argument type:
import { z } from 'zod';
import { ax, fn } from '@ax-llm/ax';
const lookupProduct = fn('lookupProduct')
.description('Look up a product by name')
.arg(z.object({
productName: z.string().min(1),
includeSpecs: z.boolean().optional(),
}))
.returns(z.object({
price: z.number(),
inStock: z.boolean(),
rating: z.number().min(1).max(5),
}))
.handler(async ({ productName, includeSpecs }) => ({
price: 79.99,
inStock: true,
rating: 4.3,
}))
.build();
const result = await gen.forward(llm, { ... }, { functions: [lookupProduct] });Running AxGen
forward()
const result = await gen.forward(llm, { input: '...' });
// With options
const result = await gen.forward(llm, { input: '...' }, {
maxRetries: 5,
model: 'gpt-4.1',
modelConfig: { temperature: 0.9, maxTokens: 1000 },
debug: true,
});Live Global Defaults
AxGen respects axGlobals for app-wide runtime defaults:
import { axGlobals } from '@ax-llm/ax';
import { trace } from '@opentelemetry/api';
const responseCache = new Map<string, any>();
axGlobals.tracer = trace.getTracer('my-app');
axGlobals.debug = true;
axGlobals.cachingFunction = async (key, value?) => {
if (value !== undefined) {
responseCache.set(key, value);
return;
}
return responseCache.get(key);
};Rules:
- Tracing/logging precedence is: forward options, then generator options, then AI service options, then current
axGlobals, then built-in defaults. abortSignalfromaxGlobalsis merged with local forward signals.customLabelsmerge from globals to AI service to forward options.cachingFunctionandfunctionResultFormatteralso fall back to currentaxGlobalswhen local options do not provide them.
streamingForward()
const stream = gen.streamingForward(llm, { input: 'Write a long story' });
for await (const chunk of stream) {
if (chunk.delta.output) process.stdout.write(chunk.delta.output);
}Stopping And Cancellation
import { AxAIServiceAbortedError } from '@ax-llm/ax';
const timer = setTimeout(() => gen.stop(), 3_000);
try {
const result = await gen.forward(llm, { topic: 'Long document' }, {
abortSignal: AbortSignal.timeout(10_000),
});
} catch (err) {
if (err instanceof AxAIServiceAbortedError) console.log('Aborted');
}Rules:
gen.stop()gracefully stops multi-step execution at the next step boundary.abortSignalcancels the underlying AI service call immediately.- Catch
AxAIServiceAbortedErrorwhen using either mechanism.
Validation, Selection, And Guards
import { ax, bestOfN, f } from '@ax-llm/ax';
import { z } from 'zod';
// Schema validation: output shape and field validity.
const gen = ax(
f()
.input('topic', z.string().min(1))
.output('summary', z.string().min(50))
.build()
);
// bestOfN: choose the best complete candidate.
const selected = bestOfN(gen, {
n: 4,
rewardFn: ({ prediction }) => prediction.summary.length,
});
// Whole-output assertion: retries with correction feedback.
gen.addAssert(
(output) => output.summary.includes(topic) || 'Summary must mention the topic.'
);
// Streaming assertion: fail fast on unsafe partial output.
gen.addStreamingAssert(
'summary',
(text) => !text.includes('forbidden'),
'Output contains forbidden text'
);Rules:
- Schema validation retries with parser/constraint feedback.
addAssert(...)checks the complete parsed output after validation/processors and retries with correction feedback on failure.bestOfN(...)scores complete candidates and returns the highest reward or first threshold hit.refine(...)runs rounds and can feed reward-derived advice into instruction components between rounds.addStreamingAssert(...)targets a string/code output field and receives partial text so far.- Streaming assertions abort the current stream attempt by throwing
AxStreamingAssertionError, then feed correction feedback into AxGen retries.
Field Processors
// Post-processing after generation
gen.addFieldProcessor('summary', (value, context) => value.toUpperCase());
// Streaming field processor (called on each chunk)
gen.addStreamingFieldProcessor('content', (partialValue, context) => {
console.log(`Received ${partialValue.length} chars`);
return partialValue;
});Rules:
addFieldProcessorruns once after the field is fully generated.addStreamingFieldProcessorruns on each streaming chunk for the target field.- Both must return the (possibly transformed) value.
Function Calling
const result = await gen.forward(llm, { question: '...' }, {
functions: tools,
functionCallMode: 'auto',
stopFunction: 'finalAnswer',
});Rules:
functionCallModecan be'auto','none', or a specific function name to force.stopFunctionaccepts a string or string[] to halt multi-step on specific function calls.- Multi-step continues until all outputs filled, stop function called, or
maxStepsreached.
Caching
Response Caching
const gen = ax('question:string -> answer:string', {
cachingFunction: async (key, value?) => {
if (value !== undefined) {
await cache.set(key, value);
return;
}
return await cache.get(key);
},
});Context Caching
const result = await gen.forward(llm, { question: '...' }, {
contextCache: { cacheBreakpoint: 'after-examples' },
});Rules:
cachingFunctionacts as a get/set: called with(key)to read,(key, value)to write.contextCacheenables AI provider-level prompt caching for long context.
Sampling And Result Picker
const result = await gen.forward(llm, { question: '...' }, {
sampleCount: 3,
resultPicker: async (samples) => {
// Evaluate each sample and return the index of the best one
return bestIndex;
},
});Rules:
sampleCountgenerates multiple completions in parallel.resultPickerreceives all samples and must return the index of the chosen result.
Extended Thinking
const result = await gen.forward(llm, { question: '...' }, {
thinkingTokenBudget: 'medium',
showThoughts: true,
});
console.log(result.thought);Rules:
thinkingTokenBudgetcan be'low','medium','high', or a number.- Set
showThoughts: trueto include the model’s reasoning inresult.thought.
Structured Outputs
const sig = f()
.input('text', f.string())
.output('summary', f.string())
.output('metadata', f.json().optional())
.useStructured()
.build();Rules:
.useStructured()asks providers with native support, including OpenAI, Anthropic, and Gemini, for schema-constrained JSON.- Native structured-output schemas list every object property in
required, setadditionalProperties: falseon objects, and express optional fields as nullable types. - Flexible
jsonfields and unshapedobjectfields are sent as JSON-encoded strings for native structured outputs, then parsed back into normal JavaScript values.
Step Hooks
const result = await gen.forward(llm, values, {
stepHooks: {
beforeStep: (ctx) => {
if (ctx.functionsExecuted.has('complexanalysis')) {
ctx.setModel('smart');
ctx.setThinkingBudget('high');
}
},
afterStep: (ctx) => {
console.log(`Usage: ${ctx.usage.totalTokens} tokens`);
},
},
});AxStepContext Read-Only Properties
stepIndex- current step numbermaxSteps- configured maximum stepsisFirstStep- whether this is the first stepfunctionsExecuted-Set<string>of function names called so farlastFunctionCalls- array of the most recent function call resultsusage- token usage statisticsstate- current step state
AxStepContext Mutators
setModel(model)- change the model for the next stepsetThinkingBudget(budget)- adjust thinking budgetsetTemperature(temp)- adjust temperaturesetMaxTokens(max)- adjust max output tokenssetOptions(opts)- set arbitrary forward optionsaddFunctions(fns)- add functions for the next stepremoveFunctions(names)- remove functions by namestop()- stop multi-step execution
Rules:
- All mutations are pending and applied at the next step boundary.
beforeStepruns before each LLM call;afterStepruns after.- Use
afterFunctionExecutionto react to specific function results.
Self-Tuning
// Simple: enable all self-tuning
const result = await gen.forward(llm, values, { selfTuning: true });
// Granular: pick what to tune
const result = await gen.forward(llm, values, {
selfTuning: {
model: true,
thinkingBudget: true,
functions: [searchWeb, calculate],
},
});Rules:
selfTuning: trueenables automatic model and parameter selection.- Granular config allows tuning specific aspects independently.
selfTuning.functionsprovides a pool of functions the tuner may add or remove per step.
Error Handling
import { AxGenerateError } from '@ax-llm/ax';
try {
const result = await gen.forward(llm, { input: '...' });
} catch (error) {
if (error instanceof AxGenerateError) {
console.log(error.details.model, error.details.signature);
}
}Rules:
AxGenerateErrorincludesdetailswithmodelandsignaturefor debugging.AxAIServiceAbortedErroris thrown on cancellation viastop()orabortSignal.
Chat Log and Usage
getChatLog()
After any .forward() or streamingForward() call, gen.getChatLog() returns the full normalized chat history — every ai.chat() round-trip, including the system prompt, all messages, and the model response. The log is reset at the start of each .forward() call. Multi-step generators (with function calls) produce one entry per step.
await gen.forward(llm, { question: 'What is 2+2?' });
for (const entry of gen.getChatLog()) {
console.log('model:', entry.model);
for (const msg of entry.messages) {
console.log(`[${msg.role}]`, msg.content);
}
console.log('tokens:', entry.modelUsage?.tokens);
}Message roles: system, user, assistant, tool. Assistant content uses inline XML:
<think>...</think>— reasoning/thinking tokens<tool_call>\n{...}\n</tool_call>— tool invocations
The system message includes a <tools> JSON block when functions are present.
type AxChatLogMessage =
| { role: 'system'; content: string }
| { role: 'user'; content: string }
| { role: 'assistant'; content: string }
| { role: 'tool'; name: string; content: string };
type AxChatLogEntry = {
name?: string;
model: string;
messages: AxChatLogMessage[];
modelUsage?: AxProgramUsage;
};
gen.getChatLog(): readonly AxChatLogEntry[]getUsage()
Returns token usage aggregated by (ai, model) across all steps. When a provider reports prompt-cache usage, promptTokens is the uncached input portion and cacheReadTokens / cacheCreationTokens carry the cache counters. Reset with resetUsage().
const usage = gen.getUsage(); // AxProgramUsage[]
console.log(usage[0]?.tokens?.promptTokens);
gen.resetUsage();AxAgent and AxFlow also return flat AxChatLogEntry[] logs; composite programs set entry.name so callers can filter by node/stage.
Examples
Fetch these for full working code:
- Streaming — field-by-field streaming
- Best Of N — reward-scored sample selection
- Refine — retry rounds with generated feedback
- Streaming Assert — fail-fast partial-output correction
- Structured Output — fluent API with validation
- Debug Logging — debug mode and step hooks
- Stop Function — stop functions
- Fibonacci — streaming with thinking
- Extraction — information extraction
- Multi-Sampling — sample count usage
Do Not Generate
- Do not use
new AxGen(...)for new code unless explicitly required. - Do not pass raw API keys or config objects where an
ai(...)instance is expected. - Do not use
forward()for streaming; usestreamingForward(). - Do not use streaming assertions as reward/refine mechanisms; they enforce hard partial-output invariants and retry with correction.
- Do not mutate step hook context expecting immediate effect; mutations are pending until the next step.
- Do not assume multi-step stops after one LLM call; it continues until outputs are filled, a stop function fires, or
maxStepsis reached.