Install any skill in seconds. Free to start, no credit card required.
Get Started Free →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.
.claude/skills/ax-llm-ax-signature/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-01 | ✗→✓ | ▲ Improved | 189% | 0% |
| case-02 | ✗→✓ | ▲ Improved | 193% | 0% |
| case-03 | ✗→✓ | ▲ Improved | 222% | 0% |
| case-10 | ✗→✓ | ▲ Improved | 255% | 0% |
| case-11 | ✗→✓ | ▲ Improved | 176% | 0% |
[description] input1:type, input2:type -> output1:type, output2:type| 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 | | 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 | :audio | input: AxAudioInput; output: AxChatAudioOutput | recording:audio, speech:audio | | 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" |
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.
typescript'tags:string[] -> processedTags:string[]' // arrays 'query:string, context?:string -> response:string' // optional with ? 'problem:string -> reasoning!:string, solution:string' // internal with !
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.
typescript`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"`
| Modifier | Applies to | Effect | |----------|-----------|--------| | min N / max N | string, number | String length bounds / numeric value bounds | | format email\|uri\|date\|date-time | string | Format validation | | pattern "regex" ["desc"] | string | Regex validation with optional description | | cache | top-level input | Prefix-cache breakpoint | | item "desc" | arrays | Per-item description: tags:string(item "a tag")[] | | <language> | code | Language of the snippet: snippet:code(python) | | true "desc" / false "desc" | boolean | Explain when each outcome applies |
Describe individual outcomes without changing a field's type or allowed values:
typescriptconst triage = ax(` ticket:string -> urgent:boolean( true "Customers cannot complete a core task", false "A routine request or minor inconvenience" ) "Does this need immediate attention?", team:class "support, billing, engineering"( support "Product usage questions", billing "Invoice or charge disputes", engineering "Broken functionality" ) "Which team should investigate?" `);
Boolean descriptions use the existing modifier bag after boolean. Class descriptions follow the quoted list of allowed labels. Quote labels containing spaces or punctuation, for example class "in progress, done"("in progress" "Work has started", done "Work is complete"). Descriptions may be partial; duplicate keys, unknown labels, and empty descriptions are errors.
The fluent equivalent uses .describeValues(...):
typescriptf.boolean('Does this need immediate attention?').describeValues({ true: 'Customers cannot complete a core task', false: 'A routine request or minor inconvenience', }); f.class(['support', 'billing'], 'Which team?').describeValues({ support: 'Product usage questions', billing: 'Invoice or charge disputes', });
OpenAI and other conventional providers receive the question followed by value: description lines in the ordinary prompt and, when used, the JSON schema description. Typesafe receives separate Noul/Choice criteria. The original annotations remain separate in the signature and survive toString() round-trips. Rendering does not mutate the signature or duplicate descriptions. Boolean and class return types, optional/array behavior, and allowed values stay unchanged. Descriptions are guidance, not additional output validation. Number bounds still describe validation constraints; Typesafe Score rubrics remain native-only. The same descriptions, validation, serialization, schemas, and prompt semantics are generated for Python, Java, C++, Go, and Rust.
For Jev, use these descriptions for boolean/class criteria. Full structured criteria, native probabilities, and Score rubrics use the separate native client; see the ax-typesafe skill. The adapter's trueThreshold is provider-wide conversion policy, not a field modifier.
object{ field:type, opt?:type } nests recursively; append [] for an array of objects.userAge?:number), never after the type.min on a boolean) is a parse error, where the fluent API silently ignores it.object{ ... }, the ! internal marker, media types, cache, and item are rejected (they only apply at the top level).\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).Real-world contracts, one line each — every entry below parses with s() as written (# lines are captions, not part of the signature):
text# 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 }[]
typescriptimport { ax, s } from '@ax-llm/ax'; const gen = ax('input:string -> output:string'); const sig = s('query:string -> response:string');
typescriptimport { 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();
.input() and .output() accept any Standard Schema v1 compatible library — no wrapper, no adapter. Three shapes work everywhere:
typescriptimport { 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:
typescript// 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:
typescriptimport { 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();
typescriptimport { 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'));
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 cachingtypescript// 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
typescriptf.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]')
typescriptf.number('age').min(18).max(120) f.number('score').min(0).max(100)
typescriptconst 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();
typescriptconst sig = f() .input('staticContext', f.string('Context').cache()) .input('userQuery', f.string('Dynamic query')) .output('answer', f.string('Response')) .build();
Good: userQuestion, customerEmail, analysisResult, confidenceScore Bad: text, data, input, output, a, x, val (too generic), 1field (starts with number)
AxChatAudioOutput.audio[] is not supported.typescript// 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'
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.f() fluent builder, NOT nested f.array(f.string()) -- those are removed.text, data, input)..internal() / { internal: true } is output-only (for chain-of-thought reasoning)..cache() / { cache: true } is input-only (for prompt caching).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.f.image() / f.audio() / f.file() — zod has no equivalent.Fetch these for full working code:
| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-05 | pass→fail | 8,997 | 7,092 | -21% | 1 | 1 | 0% | 1,996 | 6,693 | +235% | 0 | 0 | — |
case-01 | fail→pass | 9,978 | 4,348 | -56% | 1 | 1 | 0% | 2,073 | 5,998 | +189% | 0 | 0 | — |
case-02 | fail→pass | 10,421 | 8,461 | -19% | 1 | 1 | 0% | 2,432 | 7,128 | +193% | 0 | 0 | — |
case-03 | fail→pass | 9,077 | 5,658 | -38% | 1 | 1 | 0% | 1,958 | 6,305 | +222% | 0 | 0 | — |
case-04 | fail→fail | 15,460 | 15,114 | -2% | 1 | 1 | 0% | 2,859 | 8,042 | +181% | 0 | 0 | — |
case-06 | pass→fail | 12,257 | 6,764 | -45% | 1 | 1 | 0% | 2,619 | 6,421 | +145% | 0 | 0 | — |
case-07 | pass→pass | 6,119 | 3,582 | -41% | 1 | 1 | 0% | 1,370 | 5,857 | +328% | 0 | 0 | — |
case-08 | pass→pass | 7,341 | 2,217 | -70% | 1 | 1 | 0% | 1,241 | 5,485 | +342% | 0 | 0 | — |
case-09 | pass→pass | 8,826 | 3,284 | -63% | 1 | 1 | 0% | 1,605 | 5,716 | +256% | 0 | 0 | — |
case-10 | fail→pass | 8,053 | 4,206 | -48% | 1 | 1 | 0% | 1,669 | 5,932 | +255% | 0 | 0 | — |
case-11 | fail→pass | 11,910 | 4,040 | -66% | 1 | 1 | 0% | 2,129 | 5,869 | +176% | 0 | 0 | — |
case-12 | fail→pass | 9,474 | 2,963 | -69% | 1 | 1 | 0% | 1,834 | 5,672 | +209% | 0 | 0 | — |
case-13 | fail→pass | 7,891 | 3,085 | -61% | 1 | 1 | 0% | 1,395 | 5,592 | +301% | 0 | 0 | — |
case-14 | fail→pass | 11,449 | 2,844 | -75% | 1 | 1 | 0% | 1,943 | 5,610 | +189% | 0 | 0 | — |
case-15 | fail→pass | 14,543 | 2,641 | -82% | 1 | 1 | 0% | 2,687 | 5,506 | +105% | 0 | 0 | — |
case-16 | fail→pass | 10,085 | 5,207 | -48% | 1 | 1 | 0% | 1,882 | 6,281 | +234% | 0 | 0 | — |
case-17 | fail→pass | 10,412 | 5,439 | -48% | 1 | 1 | 0% | 1,866 | 6,234 | +234% | 0 | 0 | — |
case-18 | fail→pass | 10,938 | 4,384 | -60% | 1 | 1 | 0% | 2,007 | 6,008 | +199% | 0 | 0 | — |
case-19 | fail→pass | 9,088 | 3,187 | -65% | 1 | 1 | 0% | 1,657 | 5,641 | +240% | 0 | 0 | — |
case-20 | pass→pass | 7,134 | 3,948 | -45% | 1 | 1 | 0% | 1,450 | 5,924 | +309% | 0 | 0 | — |
case-21 | fail→pass | 11,382 | 4,669 | -59% | 1 | 1 | 0% | 2,150 | 6,000 | +179% | 0 | 0 | — |
case-22 | pass→pass | 11,936 | 7,721 | -35% | 1 | 1 | 0% | 2,082 | 6,592 | +217% | 0 | 0 | — |
DecimalAI ran this skill against gemini-3.6-flash twice over the same eval suite — once with the skill loaded and once without — and compared the two runs case by case. 22 cases were attempted. The headline lift of +55 percentage points is the difference between those two pass rates over the 22 comparable cases. 2 cases got worse with the skill loaded, and they are included in that figure.
The publisher has shipped newer versions since this run, so these numbers describe v1, not the version currently listed.
Without the skill loaded, the model failed this case. With it loaded, the same prompt on the same model passed. This is one improved case from the latest verified run; every case, including any that regressed, is in the table above.
Other measured skills in the registry, with their headline benchmark lift.