Ax Signature Reference
SkillAI & modelsThis skill helps an LLM generate correct DSPy signature code using @ax-llm/ax. Use when the user asks about signatures, s(), f(), field types, string syntax, fluent builder API, validation constraints, or type-safe inputs/outputs.
Available today. Use it from your connected AI after setup.
No other account needed.
Connect ahel once, and every AI you use reads what you have installed.
Then ask your AI: use the Ax Signature Reference skill
What this skill tells your AI
The instructions your AI receives, as published by diogenesoftoronto/keating in .agents/skills/ax-signature/SKILL.md and read by ahel’s review.
Signature Syntax
[description] input1:type, input2:type -> output1:type, output2:type
Field Types
| Type | Syntax | TypeScript | Example |
|---|---|---|---|
| String | :string | string | userName:string |
| Number | :number | number | score:number |
| Boolean | :boolean | boolean | isValid:boolean |
| JSON | :json | any | metadata:json |
| Date | :date | Date | birthDate:date |
| DateTime | :datetime | Date | timestamp:datetime |
| Image | :image | {mimeType, data} | photo:image (input only) |
| Audio | :audio | {format?, data} | recording:audio (input only) |
| File | :file | {mimeType, data} | document:file (input only) |
| URL | :url | string | website:url |
| Code | :code | string | pythonScript:code |
| Class | :class "a, b, c" | "a" | "b" | "c" | mood:class "happy, sad" |
Arrays, Optional, and Internal Fields
'tags:string[] -> processedTags:string[]' // arrays
'query:string, context?:string -> response:string' // optional with ?
'problem:string -> reasoning!:string, solution:string' // internal with !
Four Ways to Create Signatures
1. String-Based (Recommended for simple cases)
import { ax, s } from '@ax-llm/ax';
const gen = ax('input:string -> output:string');
const sig = s('query:string -> response:string');
2. Pure Fluent Builder API
import { f } from '@ax-llm/ax';
const sig = f()
.input('userMessage', f.string('User input'))
.input('contextData', f.string('Additional context').optional())
.input('tags', f.string('Keywords').array())
.output('responseText', f.string('AI response'))
.output('confidenceScore', f.number('Confidence 0-1'))
.output('debugInfo', f.string('Debug info').internal())
.build();
3. Standard Schema (zod / valibot / arktype)
.input() and .output() accept any Standard Schema v1 compatible library — no wrapper, no adapter. Three shapes work everywhere:
import { z } from 'zod';
import { f } from '@ax-llm/ax';
// Shape A: per-field schema — name first, then the schema, then optional ax hints
const sig = f()
.input('contextData', z.string().describe('Background context'), { cache: true })
.input('userQuestion', z.string().describe('Question to answer'))
.output('reasoning', z.string().describe('Step-by-step thinking'), { internal: true })
.output('answer', z.string().describe('Final answer'))
.build();
// Shape B: whole-object schema — decomposed into fields in declaration order
const sig2 = f()
.description('Answer questions from retrieved context')
.input(
z.object({
contextData: z.string().describe('Background context'),
userQuestion: z.string().describe('Question to answer'),
}),
{ fields: { contextData: { cache: true } } } // companion options map
)
.output(
z.object({
reasoning: z.string().describe('Step-by-step thinking'),
answer: z.string().describe('Final answer'),
}),
{ fields: { reasoning: { internal: true } } }
)
.build();
Validation constraints from zod flow into ax's prompt validation:
// String constraints: .email(), .url(), .min(), .max(), .regex()
// Number constraints: .min(), .max()
// Arrays: z.array(z.string())
// Enums: z.enum([...]) — NOTE: enum maps to ax class type, output fields only
const sig3 = f()
.input(z.object({
emailAddress: z.string().email().describe('Contact email'),
username: z.string().min(3).max(20).describe('Handle'),
score: z.number().min(0).max(100).describe('Numeric score'),
}))
.output(z.object({
priority: z.enum(['low', 'medium', 'high']).describe('Priority'),
summary: z.string().describe('Result'),
}))
.build();
Companion options (AxFieldOptions) carry ax-specific hints that schema libraries don't represent:
| Option | Effect |
|---|---|
{ cache: true } | Mark input field as a prefix-cache breakpoint |
{ internal: true } | Mark output field as internal scratchpad (stripped from result) |
The same Standard Schema shapes work on fn() tools via .arg(), .returns(), and .returnsField() — argument types are inferred from the schema:
import { z } from 'zod';
import { fn } from '@ax-llm/ax';
// Whole-object zod on a tool — AI-SDK-style
const lookupProduct = fn('lookupProduct')
.description('Look up a product by name and return its current details')
.arg(
z.object({
productName: z.string().min(1).describe('Exact product name'),
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();
// Per-argument form — mix with f.*() args, attach ax hints
const searchDocs = fn('searchDocs')
.description('Search indexed docs')
.arg('query', z.string().min(1), { cache: true })
.arg('limit', z.number().int().positive().optional())
.returnsField('results', z.array(z.string()))
.handler(async ({ query }) => [])
.build();
4. Hybrid
import { s, f } from '@ax-llm/ax';
const sig = s('base:string -> result:string')
.appendInputField('extra', f.json('Metadata').optional())
.appendOutputField('score', f.number('Quality score'));
Fluent API Reference
Type creators:
f.string(desc),f.number(desc),f.boolean(desc),f.json(desc)f.image(desc),f.audio(desc),f.file(desc),f.url(desc)f.email(desc),f.date(desc),f.datetime(desc)f.class(['a','b','c'], desc),f.code(desc)f.object({ field: f.string() }, desc)
Chainable modifiers (method chaining only, no nesting):
.optional()- make field optional.array()/.array('list description')- make field an array.internal()- output only, hidden from final output.cache()- input only, mark for prompt caching
// Correct: pure fluent chaining
f.string('description').optional().array()
f.string('context').cache().optional()
f.object({ field: f.string() }, 'item desc').array('list desc')
// Wrong: nested function calls (removed)
f.array(f.string('description')) // REMOVED
f.optional(f.string('description')) // REMOVED
f.internal(f.string('description')) // REMOVED
Validation Constraints
String Constraints
f.string('username').min(3).max(20)
f.string('email').email()
f.string('website').url()
f.string('birthDate').date()
f.string('timestamp').datetime()
f.string('pattern').regex('^[A-Z0-9]')
Number Constraints
f.number('age').min(18).max(120)
f.number('score').min(0).max(100)
Complete Validation Example
const sig = f()
.input('formData', f.string('Raw form data'))
.output('user', f.object({
username: f.string('Username').min(3).max(20),
email: f.string('Email').email(),
age: f.number('Age').min(18).max(120),
bio: f.string('Bio').max(500).optional(),
website: f.string('Website').url().optional(),
tags: f.string('Tag').min(2).max(30).array()
}, 'User profile'))
.build();
Cached Input Fields
const sig = f()
.input('staticContext', f.string('Context').cache())
.input('userQuery', f.string('Dynamic query'))
.output('answer', f.string('Response'))
.build();
Field Naming Rules
Good: userQuestion, customerEmail, analysisResult, confidenceScore
Bad: text, data, input, output, a, x, val (too generic), 1field (starts with number)
Media Type Restrictions
- Media types (image, audio, file) are top-level input fields only
- Cannot be nested in objects
- Cannot be output fields
Common Patterns
// Chain of Thought
'problem:string -> reasoning!:string, solution:string'
// Classification
'email:string -> priority:class "urgent, normal, low"'
// Multi-modal
'imageData:image, question?:string -> description:string, objects:string[]'
// Data Extraction
'invoiceText:string -> invoiceNumber:string, totalAmount:number, lineItems:json[]'
// With description
'"Answer TypeScript questions" question:string -> answer:string, confidence:number'
Critical Rules
- Use
f()fluent builder, NOT nestedf.array(f.string())-- those are removed. - Field names must be descriptive (not generic like
text,data,input). - Media types are input-only, top-level only.
.internal()/{ internal: true }is output-only (for chain-of-thought reasoning)..cache()/{ cache: true }is input-only (for prompt caching).- Validation errors trigger auto-retry with correction feedback.
f.email(),f.url(),f.date(),f.datetime()are shorthand forf.string().email()etc.z.enum()maps to ax'sclasstype — only valid on output fields.- For multimodal inputs (images, audio, files) use
f.image()/f.audio()/f.file()— zod has no equivalent.
Examples
Fetch these for full working code:
- Standard Schema (zod) — zod with f() and fn(), all three shapes
- Fluent Signature — native fluent f() API
- Structured Output — structured output with validation
- Debug Schema — JSON schema validation
Signals
- GitHub stars
- 36
- Last commit
- Sep 2026
Advanced
- Catalog kind
- skill
- Gateway key
ax-signature- Source
- github.com/diogenesoftoronto/keating