Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 50 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -158,12 +158,62 @@ const result = await generator.forward(llm, { userQuestion: "What is Ax?" });
console.log(result.responseText, result.confidenceScore);
```

### Bring Your Own Zod Schemas

Reuse existing validation schemas without rewriting them:

```typescript
import { AxSignature, ax } from "@ax-llm/ax";
import { z } from "zod";

const ticketSignature = AxSignature.fromZod({
description: "Summarize support tickets",
input: z.object({
subject: z.string(),
body: z.string(),
urgency: z.enum(["low", "normal", "high"]).optional(),
}),
output: z.object({
summary: z.string(),
sentiment: z.enum(["positive", "neutral", "negative"]),
}),
});

// Inspect any downgrades programmatically
console.log(ticketSignature.getZodConversionIssues());

// Quickly audit a schema
AxSignature.debugZodConversion({
input: z.object({
subject: z.string(),
body: z.string(),
}),
});

// Emit a warning-style report when issues exist
ticketSignature.reportZodConversionIssues();

// Need the Zod schemas back out (e.g. for adapters)?
const { input: inputSchema, output: outputSchema } = ticketSignature.toZod({
warnOnFallback: false,
});
if (inputSchema && outputSchema) {
type TicketInput = z.input<typeof inputSchema>;
type TicketOutput = z.output<typeof outputSchema>;
}

const summarize = ax(ticketSignature);
```

## Powerful Features, Zero Complexity

- ✅ **15+ LLM Providers** - OpenAI, Anthropic, Google, Mistral, Ollama, and
more
- ✅ **Type-Safe Everything** - Full TypeScript support with auto-completion
- ✅ **Streaming First** - Real-time responses with validation
- ✅ **Zod-Friendly** - Convert schemas with automatic fallbacks and warnings
- ✅ **Round-Trip Friendly** - Regenerate Zod objects from signatures when you need adapters
- ✅ **Downgrade Awareness** - Records/maps/unions stay `json` but are flagged so you can adjust early
- ✅ **Multi-Modal** - Images, audio, text in the same signature
- ✅ **Smart Optimization** - Automatic prompt tuning with MiPRO
- ✅ **Agentic Context Engineering** - ACE generator → reflector → curator loops
Expand Down
107 changes: 106 additions & 1 deletion docs/SIGNATURES.md
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,111 @@ const sig = s('base:string -> result:string')
.appendOutputField('score', f.number('Quality score'));
```

### 4. Reusing Zod Schemas

```typescript
import { AxSignature } from '@ax-llm/ax';
import { z } from 'zod';

const inputSchema = z.object({
query: z.string().describe('Search query text'),
limit: z.number().int().max(10).optional(),
tags: z.array(z.string()).optional(),
});

const outputSchema = z.object({
results: z.array(
z.object({
title: z.string(),
url: z.string().url(),
})
),
status: z.enum(['success', 'failure']),
});

const signature = AxSignature.fromZod({
description: 'Search knowledge base documents',
input: inputSchema,
output: outputSchema,
});
```

> `AxSignature.fromZod` covers the common Zod v4 primitives and warns whenever a field must be downgraded (for example, to `json`). Inputs use `z.input<>`, outputs use `z.output<>`, so you keep end-to-end type safety without duplicating schema definitions.

**Mapping highlights**

- `z.string()`, `z.number()`, `z.boolean()`, `z.date()` map to matching Ax field types. String refinements such as `.url()` and `.datetime()` become `url`/`datetime` field types.
- `z.array()` becomes Ax arrays; nested arrays fall back to `json`. Optionality flows from wrappers on the array schema (`z.array(...).optional()`), while optional elements (`z.array(z.string().optional())`) still produce required arrays at runtime.
- Literal unions/`z.enum`/`z.nativeEnum` values become classifications; input unions are exposed as `string` fields with `options` metadata (Ax intentionally disallows `class` inputs).
- Optional/nullable/default/catch wrappers automatically mark the field optional.
- Records, maps, discriminated unions, and other dynamic structures stay as `json`, but Ax marks them as **downgraded** so you can adjust the schema (or rely on `strict: true`).

**Downgrades & strict mode**

- Ax emits a console warning (once per conversion) when a field cannot be represented exactly. Pass `{ warnOnFallback: false }` to silence the warning.
- Use `{ strict: true }` to throw if any field would be downgraded. The optional `onIssues` callback receives detailed metadata for custom logging or metrics.

```typescript
const signature = AxSignature.fromZod(
{
input: inputSchema,
output: outputSchema,
},
{
strict: false,
onIssues: (issues) => {
if (issues.length > 0) {
console.warn('Downgraded Zod fields', issues);
}
},
}
);

// Issues are stored on the signature instance for later inspection.
signature.getZodConversionIssues();

// Emit a human-friendly summary (console.warn by default)
signature.reportZodConversionIssues();
```

Need a quick readout before wiring it in? Call
`AxSignature.debugZodConversion({ input, output })` to get both the signature
and a ready-made downgrade report. You can pass `{ logger: yourTelemetry }` to
pipe the downgrade issues into custom observability tooling.

**Round-trip back to Zod**

Already have a signature and need Zod validators (for adapters, loaders, or
form tooling)? Call `signature.toZod()` to reconstruct the schemas:

```typescript
import { z } from 'zod';

const { input: zodInput, output: zodOutput, issues } = signature.toZod({
warnOnFallback: false,
});

if (zodInput) {
type InputPayload = z.input<typeof zodInput>;
}
if (zodOutput) {
type OutputPayload = z.output<typeof zodOutput>;
}

if (issues.length > 0) {
console.warn('Some fields were widened for Zod conversion', issues);
}
```

Most Ax field types map cleanly (`string`, `number`, `boolean`, `image`, `file`,
etc.). We fall back to permissive schemas such as `z.any()` for types without a
direct Zod equivalent and report them through the `issues` array (or throw when
`strict: true`).

**Standard Schema?**

Standard Schema libraries (Effect Schema, Valibot, ArkType, etc.) publish adapters to Zod or JSON Schema. Convert with your preferred tool (for example [`xsschema`](https://xsai.js.org/docs/packages-top/xsschema)) and feed the resulting Zod schema into `AxSignature.fromZod`.

## Pure Fluent API Reference

The fluent API has been redesigned to be purely fluent, meaning you can only use method chaining with `.optional()`, `.array()`, and `.internal()` methods. Nested function calls are no longer supported.
Expand Down Expand Up @@ -581,4 +686,4 @@ DSPy signatures in Ax transform LLM interactions from fragile prompt engineering
- Switch LLM providers without changing logic
- Build production-ready AI features faster

Start using signatures today and experience the difference!
Start using signatures today and experience the difference!
31 changes: 29 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion src/aisdk/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@
"@ai-sdk/provider-utils": "^3.0.10",
"@ax-llm/ax": "14.0.31",
"ai": "^5.0.50",
"zod": "^3.23.8"
"zod": "^4.1.12"
},
"bugs": {
"url": "https://github.com/@ax-llm/ax/issues"
Expand Down
8 changes: 1 addition & 7 deletions src/aisdk/provider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -564,13 +564,7 @@ class AxToSDKTransformer extends TransformStream<
}
}

type AnyZod =
| z.AnyZodObject
| z.ZodString
| z.ZodNumber
| z.ZodBoolean
| z.ZodArray<AnyZod>
| z.ZodOptional<AnyZod>;
type AnyZod = z.ZodTypeAny;

function convertToZodSchema(
jsonSchema: Readonly<AxFunctionJSONSchema>
Expand Down
8 changes: 1 addition & 7 deletions src/aisdk/util.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,7 @@
import type { AxFunctionJSONSchema } from '@ax-llm/ax/index.js';
import { z } from 'zod';

type AnyZod =
| z.AnyZodObject
| z.ZodString
| z.ZodNumber
| z.ZodBoolean
| z.ZodArray<AnyZod>
| z.ZodOptional<AnyZod>;
type AnyZod = z.ZodTypeAny;

export function convertToZodSchema(
jsonSchema: Readonly<AxFunctionJSONSchema>
Expand Down
Loading