CtrlK
BlogDocsLog inGet started
Tessl Logo

ax-signature

This 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.

64

Quality

77%

Does it follow best practices?

Run evals on this skill

Adds up to 20 points to the overall score

View guide

SecuritybySnyk

Passed

No findings from the security scan

Fix and improve this skill with Tessl

tessl review fix ./website/static/typescript/.well-known/agent-skills/ax-signature/SKILL.md
SKILL.md
Quality
Evals
Security

Ax Signature Reference

Signature Syntax

[description] input1:type, input2:type -> output1:type, output2:type

Field Types

TypeSyntaxTypeScriptExample
String:stringstringuserName:string
Number:numbernumberscore:number
Boolean:booleanbooleanisValid:boolean
JSON:jsonanymetadata:json
Date:dateDatebirthDate:date
DateTime:datetimeDatetimestamp:datetime
DateRange:dateRange{ start: Date; end: Date }travelDates:dateRange
DateTimeRange:datetimeRange{ start: Date; end: Date }meetingWindow:datetimeRange
Image:image{mimeType, data}photo:image (input only)
Audio:audioinput: AxAudioInput; output: AxChatAudioOutputrecording:audio, speech:audio
File:file{mimeType, data}document:file (input only)
URL:urlstringwebsite:url
Code:codestringpythonScript:code
Class:class "a, b, c""a" | "b" | "c"mood:class "happy, sad"

Date, datetime, and range fields are AI-friendly but strict. They accept ISO-style values, trim minor whitespace/casing issues, and parse ranges as { "start": "...", "end": "..." }, [start, end], start/end, or natural delimiters like start to end; invalid values and reversed ranges should fail validation rather than being silently autocorrected.

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 !

Extended String Grammar (Modifier Bags + Nested Objects)

The string form is constraint-complete: everything the fluent API expresses (except Standard Schema fields) can be written in the string. A type takes an optional comma-separated, order-free modifier bag in parentheses, and objects declare structured fields inline.

`userAge:number(min 0, max 120), contactEmail:string(format email, cache), codeSnippet:code(python)
 -> userName:string(pattern "^[a-z_]+$" "lowercase name"), tagList:string(item "a short tag")[] "all tags",
    profileList:object{ fullName:string, userAge?:number(min 0) }[] "matched profiles"`
ModifierApplies toEffect
min N / max Nstring, numberString length bounds / numeric value bounds
format email|uri|date|date-timestringFormat validation
pattern "regex" ["desc"]stringRegex validation with optional description
cachetop-level inputPrefix-cache breakpoint
item "desc"arraysPer-item description: tags:string(item "a tag")[]
<language>codeLanguage of the snippet: snippet:code(python)
  • object{ field:type, opt?:type } nests recursively; append [] for an array of objects.
  • Optional goes on the name (userAge?:number), never after the type.
  • The string API is strict: a modifier that does not apply to its type (e.g. min on a boolean) is a parse error, where the fluent API silently ignores it.
  • Inside object{ ... }, the ! internal marker, media types, cache, and item are rejected (they only apply at the top level).
  • In quoted values, backslashes are doubled — a regex \d is written pattern "\\d+".
  • AxSignature.toString() renders every construct back to this grammar losslessly, so a signature round-trips — this is what lets a whole flow serialize its node contracts into mermaid %%ax directives (see the ax-flow skill).

Signature Gallery

Real-world contracts, one line each — every entry below parses with s() as written (# lines are captions, not part of the signature):

# Support triage: several class outputs plus a capped reply draft
ticketText:string -> priorityClass:class "p0, p1, p2", sentimentClass:class "angry, neutral, happy", replyDraft:string(max 500)

# Invoice extraction: regex-validated id, bounded totals, structured line items
invoiceText:string -> invoiceNumber:string(pattern "^INV-\\d+$" "INV- then digits"), totalAmount:number(min 0), lineItems:object{ description:string, quantity:number(min 1), unitPrice:number }[]

# Contact enrichment: optional format-validated outputs
bioText:string -> contactEmail?:string(format email), websiteUrl?:string(format uri), birthDate?:string(format date)

# RAG: cached corpus input plus per-item described citations
corpusText:string(cache), userQuestion:string -> answerText:string, citedChunks:string(item "verbatim quote")[]

# Code generation: language-tagged code outputs
taskBrief:string -> pythonScript:code(python), testCases:code(python), riskNotes?:string

# Chain of thought: internal reasoning stripped from the result
problemText:string -> reasoning!:string, solutionText:string

# Resume parsing: nested objects inside nested arrays
resumeText:string -> candidateProfile:object{ fullName:string, yearsExperience:number(min 0), skillList:string[], education:object{ schoolName:string, degreeName?:string }[] }

# Lead scoring: signature-level description, bounded score, class next step
"Score sales leads" leadNotes:string -> fitScore:number(min 0, max 100) "0-100 fit", nextStep:class "call, email, drop"

# Multimodal: top-level image input with an optional question
productPhoto:image, question?:string -> productDescription:string, detectedObjects:string[]

# Meeting audio: audio input, capped summary, per-item action list
meetingAudio:audio -> meetingSummary:string(max 1000), actionItems:string(item "one action item")[]

# Moderation: class verdict plus structured flagged spans
postText:string -> moderationVerdict:class "allow, review, block", flaggedSpans:object{ spanText:string, reasonNote:string }[]

# Translation: optional locale input
sourceText:string, targetLocale?:string -> translatedText:string, glossaryHits:string[]

# Text-to-SQL: cached schema plus SQL-tagged output
schemaText:string(cache), questionText:string -> sqlQuery:code(sql), queryNotes?:string(max 200)

# Calendar extraction: datetime fields and an optional end
emailText:string -> eventTitle:string, startsAt:datetime, endsAt?:datetime, attendeeNames:string[]

# Booking window: date range, bounded party size, and flexibility flag
requestText:string -> stayWindow:dateRange, partySize:number(min 1, max 12), flexibleDates:boolean

# Contract dates: date fields plus bounded notice period
contractText:string -> effectiveDate:date, expiryDate?:date, autoRenews:boolean, noticeDays?:number(min 0)

# Link audit: URL arrays and an optional primary URL
pageText:string -> referencedUrls:url[], primaryUrl?:url

# Config generation: JSON output plus per-item warnings
requirementsText:string -> serviceConfig:json, setupWarnings:string(item "one warning")[]

# Claims gate: cached policy, bounded confidence, and optional citation
claimText:string, policyText:string(cache) -> isCovered:boolean, confidenceScore:number(min 0, max 1), citedClause?:string

# Earnings extraction: structured period data plus a class outlook
filingText:string(cache) -> revenueByPeriod:object{ periodLabel:string, amountUsd:number }[], guidanceTone:class "raise, hold, cut"

# Pull request review: diff code, cached guide, structured comments, and verdict
diffText:code(diff), styleGuide?:string(cache) -> reviewComments:object{ filePath:string, lineNumber:number(min 1), commentText:string(max 300) }[], overallVerdict:class "approve, revise"

# Incident triage: severity class, optional service, and per-item runbook steps
alertLog:string -> incidentSeverity:class "sev1, sev2, sev3", suspectedService?:string, runbookSteps:string(item "one step")[]

# Product listing: image and file inputs with constrained listing outputs
productPhoto:image, priceSheet?:file -> listingTitle:string(max 80), bulletPoints:string(item "one selling point")[], priceUsd?:number(min 0)

# Study cards: nested object array with an optional difficulty tag
chapterText:string -> flashCards:object{ questionText:string, answerText:string, difficultyTag?:string }[]

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:

OptionEffect
{ 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.dateRange(desc), f.datetimeRange(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

  • Image and file fields are top-level input fields only.
  • Audio fields can be top-level inputs or single top-level outputs.
  • Audio output fields are scripted speech artifacts: the model returns plain text, then Ax synthesizes AxChatAudioOutput.
  • Media fields cannot be nested in objects.
  • Media arrays are supported for inputs only; output audio[] is not supported.

Common Patterns

// Chain of Thought
'problem:string -> reasoning!:string, solution:string'

// Classification
'email:string -> priority:class "urgent, normal, low"'

// Multi-modal input
'imageData:image, question?:string -> description:string, objects:string[]'

// Scripted speech output
'question:string -> speech:audio, summary:string'

// Data Extraction
'invoiceText:string -> invoiceNumber:string, totalAmount:number, lineItems:json[]'

// Constrained string form (no fluent builder needed)
'reviewText:string(max 2000) -> rating:number(min 1, max 5), themes:string(item "a theme")[]'

// Nested object output in the string form
'profileText:string -> profile:object{ fullName:string, age?:number(min 0) }'

// With description
'"Answer TypeScript questions" question:string -> answer:string, confidence:number'

Critical Rules

  • The string form is constraint-complete: reach for modifier bags (string(max 500), number(min 0, max 10), string(format email)) and inline object{ ... } before switching to fluent/zod just for constraints. Reserve fluent/Standard Schema for zod/valibot-backed fields.
  • The string API is strict — a modifier that does not apply to its type is a parse error (the fluent API silently ignores it).
  • Use f() fluent builder, NOT nested f.array(f.string()) -- those are removed.
  • Field names must be descriptive (not generic like text, data, input).
  • Image/file media types are input-only, top-level only; audio may also be a single top-level output.
  • .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 for f.string().email() etc.; f.dateRange() and f.datetimeRange() return { start: Date; end: Date }.
  • z.enum() maps to ax's class type — only valid on output fields.
  • For multimodal inputs (images, audio, files) and scripted audio outputs, use f.image() / f.audio() / f.file() — zod has no equivalent.

Examples

Fetch these for full working code:

Repository
ax-llm/ax
Last updated
First committed

Is this your skill?

If you maintain this skill, you can claim it as your own. Once claimed, you can manage eval scenarios, bundle related skills, attach documentation or rules, and ensure cross-agent compatibility.