A typed answer
Give Signal a Zod schema and the answer is an object of that shape. One that fails the schema is retried, with what was wrong.
pnpm add @niscorp/signal zod openaiThe client
createSignal names a provider and gives you a client. The key is read from GROQ_API_KEY when a call is made, and the model is the provider’s default unless you name one with .model().
import { z } from 'zod';
import { SignalError, createSignal } from '@niscorp/signal';
const signal = createSignal('groq');The shape
The schema is what you want back. A describe on a field is sent to the model with it.
const Booking = z.object({
show: z.string(),
seats: z.number().describe('How many seats, as a number'),
});A client that answers in that shape
Every method that sets something returns a new client and leaves the old one as it was. schema gives one whose answers are bookings.
onRetry is called each time an answer fails the schema, the last time included.
const reader = signal
.schema(Booking)
.onRetry((error, attempt) => console.log('failed', attempt, error.message));The call
Signal sends the schema the way this model takes one, reads the JSON out of what comes back, and checks it against the schema. response is typed as a booking.
An answer that fails is retried: the model is sent what was wrong and asked again, twice by default (.retries(n) changes that). After that the call throws a SignalError whose message says what was wrong.
try {
const { response: booking, meta } = await reader.complete('Two for The Tempest on Saturday, please.');
console.log(booking.show, booking.seats);
console.log('retries:', meta.retries, '· tokens:', meta.usage.totalTokens);
} catch (error) {
if (!(error instanceof SignalError)) throw error;
console.log(error.code, error.message);
}Run it.
This file asks a model, so it is not run on this site. When the model answers well it prints The Tempest 2, then the number of retries and the tokens used. A model that writes the seats as a word prints failed 1 … first and is asked again. One that never passes ends with E_VALIDATION_FAILED and what was wrong.
GROQ_API_KEY=your-key npx tsx booking.mts