DSPy The Ax take on DSPy-style typed LLM programs. typescript concepts concepts/dspy website/content-src/templates/concept-dspy.md concepts DSPy

DSPy

DSPy-style programming treats prompts as programs. Instead of writing one giant prompt and hoping the output format survives, you declare the shape of the task, run it against a model, observe failures, add examples, and optimize the program.

In Ax, the practical unit is a signature. A signature gives generation, validation, tools, traces, examples, optimizers, and generated language packages the same semantic contract.

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

const llm = ai({ name: 'openai', apiKey: process.env.OPENAI_APIKEY! });
const qa = ax('question:string -> answer:string, confidence:number');
const out = await qa.forward(llm, { question: 'What is Ax?' });

That signature says the program receives question and must return both answer and confidence. Ax turns that contract into prompts, output parsing, validation, retries, traces, and optimization inputs.

flowchart LR
  A["Declare signature"] --> B["Run program"]
  B --> C["Observe traces + failures"]
  C --> D["Add examples"]
  D --> E["Optimize with a metric"]
  E --> F["Redeploy artifact"]
  F --> B

How Ax Splits The Work

Ax keeps the model boundary explicit:

  • ai() owns provider setup, model selection, routing, streaming, media, embeddings, thinking, and usage.
  • s() parses reusable signatures when you need to inspect or compose contracts.
  • ax() runs a structured generation program from a signature.
  • agent() runs an RLM agent where model-written actor steps use tools, child agents, discovery, memory, and a runtime session.
  • optimize() tunes programs with examples, metrics, GEPA, and Pareto-aware artifacts.

Why It Matters

  • Prompts become typed contracts instead of string piles.
  • Examples and traces become reusable training and evaluation data.
  • Validation failures are concrete, so retries can feed useful correction messages back into the model.
  • Tools and agents share the same input/output vocabulary as generation.
  • Generated language packages preserve the Ax semantic contract without pretending to transpile TypeScript.

What Changes Compared To Prompt Strings

With a hand-written prompt, your app usually owns the fragile parts: output parsing, schema checks, retry prompts, examples, metrics, and logs. With Ax, those pieces attach to the program. The prompt is still there, but it is generated from a contract and can be optimized with measured feedback.

That is why Ax docs talk about programs instead of prompt templates. A program can be run, traced, tested, compiled with demos, and improved.

From contract to 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] });

From examples to optimization

TypeScript
const classifier = ax('emailText:string -> priority:class "high, normal, low"');
const metric = ({ prediction, example }) =>
  prediction.priority === example.priority ? 1 : 0;

const result = await optimize(classifier, train, metric, {
  studentAI,
  teacherAI,
  validationExamples,
  maxMetricCalls: 120,
});
classifier.applyOptimization(result.optimizedProgram!);

Where TypeScript Fits

TypeScript is the reference implementation. This TypeScript page swaps in language-specific install commands, snippets, and API names where the package surface differs. The goal is not to run the original TypeScript app in another language; generated packages expose native surfaces for the shared Ax semantic contract.

See signatures and ax() generation.

Docs