An answer that fits
Give an agent a schema and its answer is data of that shape. An answer that does not fit goes back to the model, in the same run.
pnpm add @niscorp/cortex @niscorp/signal zod openaiThe model
createSignal makes the client that calls the model. Cortex is handed this client, here for Groq, and Signal reads the key from GROQ_API_KEY.
import { z } from 'zod';
import { createSignal } from '@niscorp/signal';
import { defineAgent, outputRetries, stepCount } from '@niscorp/cortex';
const llm = createSignal('groq');The shape of the answer
A Zod schema says what the answer has to be. Here, an order read out of a sentence: what was ordered, and how many.
const Order = z.object({
item: z.string(),
quantity: z.number().int().positive(),
});Your own check
validate is your own check, run after the schema passes. It can look things up. It answers { ok: true }, or { retry } with a sentence for the model.
Both kinds of refusal are handled the same way: the model’s answer and what was wrong with it are added to the conversation, and the model answers again. The run is not started over.
const menu = ['coffee', 'tea', 'cake'];
const reader = defineAgent({
id: 'order-reader',
instructions: 'Read the order out of the message.',
output: {
schema: Order,
validate: (output) =>
menu.includes(output.data.item)
? { ok: true }
: { retry: `We do not sell ${output.data.item}. The menu is: ${menu.join(', ')}.` },
},
stopWhen: [stepCount(6), outputRetries(3)],
});Run it, and watch for corrections
Every refusal is a retry event, carrying what the model was told. result.meta.outputRetries counts them.
outputRetries(3) ends the run at the third refused answer, with result.ok false and the last answer in result.error.lastOutput.
const run = reader.run('Two flat whites please', { llm });
run.onEvent((event) => {
if (event.type === 'retry') console.log('sent back:', event.issues);
});
const result = await run.result;
if (result.ok) console.log(result.output.data, 'after', result.meta.outputRetries, 'corrections');
else console.log(result.error.code, result.error.message);Run it.
This file asks a model, so it is not run on this site. When the model first answers with an item that is not on the menu, it prints sent back: and the sentence from validate, and then an order for coffee after 1 correction. result.output.data is typed as the schema’s shape.
GROQ_API_KEY=your-key npx tsx answer.mts