Signal · guide

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.

Install it
pnpm add @niscorp/signal zod openai
Step 1 of 4

The 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().

The client
import { z } from 'zod';
import { SignalError, createSignal } from '@niscorp/signal';

const signal = createSignal('groq');
Step 2 of 4

The shape

The schema is what you want back. A describe on a field is sent to the model with it.

The shape
const Booking = z.object({
  show: z.string(),
  seats: z.number().describe('How many seats, as a number'),
});
Step 3 of 4

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.

A client that answers in that shape
const reader = signal
  .schema(Booking)
  .onRetry((error, attempt) => console.log('failed', attempt, error.message));
Step 4 of 4

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.

The call
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);
}
Then

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.

Run it
GROQ_API_KEY=your-key npx tsx booking.mts