Tools
A method the model may call: its docstring, its arguments, confirmation, masking and timeouts.
A tool is a method the model may call. You write its description as a docstring and its arguments as the method's parameters; Pinecall builds the schema, runs the call and logs it.
What is a tool?#
A method decorated with @tool(). When the model calls it, the method runs in your process with
the arguments the model gave, and what it returns is what the model reads next.
/** Free slots on one day, for one specialty. Always call it for a day the patient names. */
@tool({ stage: ["choose", "book"], preview: 2 })
async freeSlots(day: string, specialty: string): Promise<Slot[]> {
const date = dayNamed(day, this.call.today ?? "");
if (!date) throw new NotADay(day);
this.slots = await this.agenda().free(date, specialty);
this.stage = this.slots.length > 0 ? "book" : "choose";
return this.slots;
}
@tool(stage=("choose", "book"), preview=2)
def free_slots(self, day: str, specialty: str) -> list[dict]:
"""Free slots on one day, for one specialty. Always call it for a day the patient names."""
date = day_named(day, self.call.today or "")
if not date:
raise NotADay(day)
self.slots = self.agenda.free(date, specialty)
self.stage = "book" if self.slots else "choose"
return self.slots
The docstring is the description, an Args: section describes each parameter, and the
annotations are the types: a Literal is an enum, a default makes a parameter optional. The
options are the same, as keywords: when=, stage=, confirm=, announce=, preview=, pii=,
timeout=.
A def tool runs on a thread, an async def one on the event loop.
# Free slots on one day, for one specialty. Always call it for a day the patient names.
tool stage: %i[choose book], preview: 2
def free_slots(day:, specialty:)
date = day_named(day, call.today.to_s)
raise NotADay, day unless date
self.slots = agenda.free(date, specialty)
self.stage = slots.any? ? :book : :choose
slots
end
The comment above tool is the description, and the keyword arguments are the parameters. The
options are the same: when:, stage:, confirm:, announce:, preview:, pii:, timeout:, and params:
for the types ({ day: String }).
How it works#
- The docstring is the description, and it is required. Without one no model can choose the
tool, so the class is refused before a call happens. It is read as one line: line breaks are
joined, and the first
@tagline ends it. - The signature is the schema. Parameter names and types become the JSON Schema the model fills.
string,number,booleanand arrays of them are typed; an interface or a type declared in the same file is read field by field; any other type becomes an open object. An optional parameter is not required. - The arguments are checked before the method runs. A missing required argument, or a value of the wrong JSON type, fails the call without running the method.
- Writes inside a tool are authored by the tool. The change log names it, and so does the console.
Options#
| option | what it does |
|---|---|
when: (state) => boolean |
shows the tool only while this returns true for the current state |
stage: "book" or ["choose", "book"] |
shows the tool only in these stages. See Stages |
confirm: "Booked: {{result.when}}." |
a sentence read to the caller after the tool runs, and it marks the tool irreversible |
announce: "Let me check the agenda." |
what the agent says as the tool starts, when the model's turn said nothing itself |
preview: 2 |
how many items of an array result the model sees. The state keeps them all |
pii: ["name", "phone"] |
parameters masked in the call's log |
timeout: 8 |
seconds Pinecall waits for the method. 30 unless set, 300 at most |
params: z.object({ … }) |
an explicit zod schema, when the signature is not enough |
when and stage together must both hold.
Confirmation#
confirm is a receipt. Once the tool has run, Pinecall fills the sentence and says it, word for
word, before the model replies. {{name}} is filled from an argument, and {{result.when}} from the
tool's return value:
@tool({ stage: "book", confirm: "Booked: {{result.when}} with {{result.professional}}." })
async book(slot: string): Promise<Booking> { … }
confirmdoes not hold the tool until the caller agrees. The tool has already run when the sentence is said, so write it as a receipt, never as a question like "Shall I book it?". Make the model ask before it calls the tool: say so in the docstring and in the view.
Announcing a tool#
A tool that takes a second or two leaves the caller in silence, or on the hold melody. announce
gives the agent a sentence to say as the tool starts:
@tool({ stage: "choose", announce: "Let me check the agenda." })
async freeSlots(day: string): Promise<Slot[]> { … }
It is said only when the model's turn said nothing itself. A model that answers "One moment, I will look at the agenda" and calls the tool in the same turn has already announced it, so the sentence stays unsaid: the caller never hears it twice. In a written chat it is a line of the agent's like any other.
A tool with confirm is declared irreversible to the gateway; every other tool is read. You do
not set this yourself.
When a tool fails#
A tool that throws does not crash the call. The error's message becomes the tool's result, marked as an error, and the model reads it and can act on it. Throw a sentence a model can use, such as "Tuesday has no free slots", never a stack trace.
Ask the model for a word, not a record. book(slot: string), with the method looking the id up
among the slots it offered, is safer than book(slot: Slot). Asked for a whole object, a model fills
in one it was never offered.
Refused when the class loads#
Each of these stops the class before any call, with a message that says what to write:
| mistake | message |
|---|---|
| no docstring | tool book: without a docstring no model can choose it; write a /** one line */ above the method |
| a name that is not one word | a tool name is one word a model can call, not … |
pii names a parameter the tool does not have |
tool book: pii names parameters the tool has; unknown: … |
stage on a class with no stage field |
tool book: stage names a value of this agent's own stage field, and … declares none |
@tool on something that is not a method |
@tool goes on a method; … is not one |