Skip to content

Function calling

A call intent lets a model fill arguments for an existing function. Nola resolves all extractor arguments in one provider call, validates them, then invokes the function with those values.

Start with a TypeScript function that creates a ticket:

src/tickets.ts
export async function createTicket(title: string, priority: number): Promise<string> {
const res = await fetch("https://example.test/tickets", {
method: "POST",
body: JSON.stringify({ title, priority }),
});
return (await res.json()).id as string;
}

Import the helper into a .tsi file and use an extractor for the argument the model should supply. This example keeps the priority fixed at 2:

src/file-ticket.tsi
import { createTicket } from "./tickets.js";
export infer function fileTicket(.request: string) {
// Sigil-less: the extractor argument makes this call an intent. `2` is a
// plain argument and is passed through untouched.
const id = ask createTicket(`a short ticket title for the request`: string, 2);
return id;
}

The call intent awaits createTicket, so id has type string. You do not need another await around the ask.

To give the model an instruction for the whole call, put a template literal after the function name:

src/file-ticket-carefully.tsi
import { createTicket } from "./tickets.js";
export infer function fileTicketCarefully(.request: string) {
return ask createTicket`file the ticket exactly as the customer described it`(
`a short ticket title`: string,
`priority 1-5, where 1 is most urgent`: number,
);
}

Every extractor argument needs an explicit type; an untyped template in the argument list is a plain string, not an extractor, and an untyped ..`…` slot raises NOLA2004. The model receives the function name, each extractor’s type and instruction, and the call hint. It fills the extractor arguments together in one response. Plain arguments are passed to the function without being shown to the model.

The function runs as part of resolving the ask. If it runs and then the attempt fails, .withRetry(n) can run it again. Use intent retries only when repeated calls are safe.

To keep the helper outside each extraction’s retry loop, extract the arguments first and call it separately:

src/file-ticket-split.tsi
import { createTicket } from "./tickets.js";
export infer function fileTicketOnce(.request: string) {
const title = ask `a short ticket title for the request`: string;
const priority = ask `priority 1-5, where 1 is most urgent`: number;
return await createTicket(title, priority); // plain await — runs exactly once
}

The helper runs once per execution of this function body. Retrying the whole fileTicketOnce intent can still repeat it, so splitting the asks does not guarantee exactly-once side effects.

ask.timeoutMs and .withTimeout limit provider round trips. They do not cancel a function that has already started running. Give the helper its own timeout or cancellation handling if it needs one. See Ask options.

src/main.ts
import { fileTicket } from "./file-ticket.tsi";
const id = await fileTicket("Checkout is down for every customer in the EU — please treat as urgent.");
console.log("created", id);

See Call intents for all three spellings and the rules that distinguish an intent from a plain function call.

Next: Testing