ax() Generation Run structured generation with tools, streaming, validation, traces, and usage. typescript subsystems subsystems/ax website/content-src/templates/subsystem-ax.md subsystems ax() Generation

ax() Generation

Use ax() to create a structured generation program from a signature.

TypeScript
import { ai, ax, f, fn } from '@ax-llm/ax';

const llm = ai({ name: 'openai', apiKey: process.env.OPENAI_APIKEY! });
const lookup = fn('lookup')
  .description('Look up a document')
  .arg('query', f.string('Search query'))
  .returnsField('result', f.string('Matching document text'))
  .handler(async ({ query }) => ({ result: `doc for ${query}` }))
  .build();
const answer = ax('question:string -> answer:string');
await answer.forward(llm, { question: 'Find the refund policy' }, { functions: [lookup] });

Generation owns prompt construction, parsing, validation, retries, tools, streaming folds, traces, usage, demos, and field processors. It is the smallest useful Ax program.

What It Does

ax() turns a signature into a runnable program. The program receives typed inputs, sends a provider request, parses structured outputs, validates fields, retries on parse or assertion failures, records traces/usage, and can call tools across multiple steps.

flowchart LR
  A["Signature"] --> B["Prompt render"]
  B --> C["Provider call"]
  C --> D["Streaming parse"]
  D --> E["Validate + assert"]
  E -->|pass| F["Typed output"]
  E -->|fail| G["Correction feedback"]
  G --> C

Core Call Shape

text
program = ax(signature, options)
result = program.forward(aiClient, inputs, runOptions)

Use streamingForward() when you need incremental output. Use forward() when the caller needs one parsed result object.

Common Patterns

  • Inline a string signature for small tasks.
  • Pass a reusable s() signature when the contract is shared.
  • Add tools when the program needs host data or side-effect boundaries.
  • Add validation through fluent fields or Standard Schema where supported.
  • Add assertions for whole-output invariants that should retry with feedback.
  • Add streaming assertions for unsafe partial text.
  • Use field processors for post-processing or streaming transforms.

Tool-backed generation

TypeScript
import { ai, ax, f, fn } from '@ax-llm/ax';

const searchDocs = fn('searchDocs')
  .namespace('kb')
  .description('Search product documentation')
  .arg('query', f.string('Search query'))
  .returns(f.string('Matching snippets').array())
  .handler(async ({ query }) => ['hit for ' + query])
  .build();

const answer = ax('question:string -> answer:string');
await answer.forward(llm, { question: "How do refunds work?" }, { functions: [searchDocs] });

MCP-backed generation

MCP servers become ordinary Ax functions after initialization. Use this when a direct ax() program needs a small set of external tools, prompts, or resources but does not need a full agent loop.

TypeScript
import { AxMCPClient, ax } from '@ax-llm/ax';

const mcpClient = new AxMCPClient(transport);
await mcpClient.init();

const answer = ax('question:string -> answer:string');
const out = await answer.forward(
  llm,
  { question: 'What did we store about the release plan?' },
  { functions: mcpClient.toFunction() }
);

For large MCP servers, keep the direct ax() path narrow. Prefer an agent with discovery when the server exposes many tools or when the model must plan multiple calls.

Streaming output

TypeScript
const write = ax('topic:string -> article:string');
const stream = write.streamingForward(llm, { topic: 'runtime agents' });

for await (const chunk of stream) {
  if (chunk.delta.article) process.stdout.write(chunk.delta.article);
}

Production Notes

Keep signatures small and specific. Put provider and model policy in ai() or forward options. Trace parse failures, retries, tool calls, max-step exits, token usage, and final parsed output shape. For tasks that need planning, memory, clarification, or delegation, move up to agent().

See ax() API, Tools, Signatures, and MCP.

Docs