AI engineering path · Models in your system · Topic 5 of 12

Tool use: the model asks, your code runs

Not started

Tool use (also called function calling) lets a model do more than write text. You list the tools it may use, each with a name, a description and a JSON Schema for its arguments. When the model decides a tool would help, its response contains a tool call instead of (or as well as) text. Your code runs the tool, sends the result back, and the model carries on. The model proposes; your application disposes.

Why it matters: Tools are how a model reads live data and takes actions. The model never runs anything itself; your code decides what executes, which is where both the power and the safety controls live.

  1. 1You describe tools

    Each tool has a name, a description and a JSON Schema for its arguments. Descriptions are prompts; write them carefully.

  2. 2The model only asks

    Instead of text, the model can return a tool call with arguments. Your code validates them and decides whether to run it.

  3. 3Results go back as messages

    You append the call and its result to the conversation and call the model again, until it answers in plain text.

  4. 4Bound the loop

    Cap the number of rounds, time out slow tools, and return errors as results so the model can recover.

1

How it works

The explanation, with examples you can run.

The loop

  1. Send the conversation plus tool definitions.
  2. If the response asks for one or more tool calls (providers mark this with a stop reason such as tool_use or a tool_calls field), validate the arguments, run each tool, and append a result message that references the call's id.
  3. Call the model again with the extended conversation.
  4. Repeat until the model replies with a final answer, or until your own limit stops it.

The Try it block plays this loop with a scripted fake model, so you can see exactly which messages travel each way.

⌘/Ctrl + Enter
OutputNot run yet

Press Run to see what your code prints, in order.

Runs in this browser, in an isolated frame with no access to this page. console.log, info, warn and error are shown; code runs as a classic script, so use an async function for await.

Designing tools

  • Descriptions are prompts. Say what the tool does, when to use it and when not to, and what each argument means, with formats ("ISO 8601 date", "order id like A-1234").
  • Few, clear tools beat many overlapping ones. If two tools could both answer a question, the model will sometimes choose the wrong one. Merge or rename them.
  • Return what the model needs, not everything you have. Trim large responses; tool results count as input tokens on the next call.
  • Make side effects explicit. Separate read tools from write tools. For writes that matter (payments, emails, deletions), require confirmation from the user or a human, and make them idempotent so a repeated call does no harm.
  • Several calls at once. Many APIs let the model request several independent tool calls in one turn. Run them concurrently if they are independent, and return every result.

Your code is the security boundary

The arguments come from a model whose input may include user text and retrieved documents, so treat them exactly like a request from an untrusted client: validate against the schema, check the current user's permissions, apply rate limits, and log what ran. Never let the model choose which credentials to use; give tools only the access they need. A prompt can describe rules, but only code enforces them.

// Placeholder client and types; the shape is common across providers.let messages: Message[] = [{ role: "user", content: question }]for (let round = 0; round < MAX_TOOL_ROUNDS; round++) {  const res = await llm.generate({ model: MODEL, system: SYSTEM, tools: TOOL_DEFINITIONS, messages })  if (res.stopReason !== "tool_use") return res.text  messages = [...messages, res.asAssistantMessage()]  const results = await Promise.all(res.toolCalls.map((call) => runToolSafely(call, currentUser)))  messages = [...messages, toolResultMessage(results)]}throw new Error("Tool loop did not finish")
2

Check yourself

3 questions. Get them all right to finish this topic.

  1. 1.The model returns a tool call: get_order({ order_id: "A-1001" }). What happens next?
  2. 2.A tool throws because the order does not exist. What is usually the best thing to send back?
  3. 3.Why put the user's permissions check in your tool code rather than in the prompt?

0 of 3 right so far. Get all three to finish this topic.