# Tool use (function calling) in LLMs, explained with code

> How LLM tool use works: the model asks to call your function, your code runs it and returns the result. A TypeScript loop for the Claude API and the mistakes to avoid.

Source: https://devaiper.com/blog/tool-use-function-calling-explained
Published: 2026-10-08
Topics: Tool use, Claude API, Agents, TypeScript

**Short answer:** The model never runs your code. It returns a tool_use request with a name and arguments, you execute the function, send a tool_result back, and repeat until the model gives a final answer.

An LLM cannot check the weather, query your database or send an email. It can *ask you* to. **Tool use** (also called function calling) is that conversation: you describe functions, the model requests one, you run it, and you hand the result back.

## The loop

> **Diagram:** The tool use loop. Your app sends the conversation and tool definitions to the model. The model replies with a tool_use request. Your app runs the function and sends back a tool_result. This repeats until the model replies with a final answer.
> Send messages + tool list (your app) → ... → Model asks for a tool (name + arguments) → ... → Run it, return the result (your app)
> Your code does the doing. The model does the deciding.

## Step 1: describe the tool

A tool is a name, a description the model reads to decide when to use it, and a JSON schema for the inputs.

```ts
import Anthropic from "@anthropic-ai/sdk";

const client = new Anthropic();

const tools: Anthropic.Tool[] = [
  {
    name: "get_order_status",
    description:
      "Look up the status of a customer order by its ID. Use when the user asks where their order is.",
    input_schema: {
      type: "object",
      properties: {
        order_id: { type: "string", description: "The order ID, e.g. A-1042" },
      },
      required: ["order_id"],
    },
  },
];
```

The **description is the most important line**. It is how the model chooses.

## Step 2: run the loop

```ts
async function getOrderStatus(orderId: string) {
  // your real code: DB query, API call...
  return { order_id: orderId, status: "shipped", eta: "2026-10-12" };
}

const messages: Anthropic.MessageParam[] = [
  { role: "user", content: "Where is order A-1042?" },
];

while (true) {
  const response = await client.messages.create({
    model: "claude-opus-5-5",
    max_tokens: 4096,
    tools,
    messages,
  });

  messages.push({ role: "assistant", content: response.content });

  if (response.stop_reason !== "tool_use") {
    for (const block of response.content) {
      if (block.type === "text") console.log(block.text);
    }
    break;
  }

  const results: Anthropic.ToolResultBlockParam[] = [];
  for (const block of response.content) {
    if (block.type !== "tool_use") continue;
    try {
      const input = block.input as { order_id: string };
      const data = await getOrderStatus(input.order_id);
      results.push({ type: "tool_result", tool_use_id: block.id, content: JSON.stringify(data) });
    } catch (err) {
      results.push({
        type: "tool_result",
        tool_use_id: block.id,
        content: `Error: ${(err as Error).message}`,
        is_error: true,
      });
    }
  }

  messages.push({ role: "user", content: results });
}
```

What happens: the model returns a `tool_use` block with `name` and `input`; `stop_reason` is `"tool_use"`. You run the function and return a matching `tool_result` (same `tool_use_id`). The model reads it and answers.

## Rules that save you hours

1. **Return all results in one user message.** If the model made several tool calls at once, send one message with every `tool_result`.
2. **Report errors, do not throw.** `is_error: true` lets the model recover or explain.
3. **Parse inputs, never trust them.** The arguments are model output. Validate them, and never build file paths, SQL or shell commands from them without checks. See [MCP security](https://devaiper.com/blog/mcp-security-risks).
4. **Write descriptions for a stranger.** Say what it does, when to use it, what it returns.
5. **Keep results small.** Big tool output fills the context. See [context engineering](https://devaiper.com/blog/what-is-context-engineering).
6. **Prefer `strict: true`** on tools when you need arguments to match the schema exactly.

## You rarely need to hand-write the loop

The SDK has a tool runner helper that runs this loop for you from typed tool functions. Write the manual loop once so you understand it, then use the helper.

## Tool use, agents and MCP

A loop with tools is the core of every [agent](https://devaiper.com/blog/ai-agents-vs-workflows). [MCP](https://devaiper.com/blog/mcp-vs-api) is how you avoid re-describing the same tools in every app: a server exposes them, any client lists and calls them. Same idea, standardized.

## FAQ

### What is tool use or function calling in an LLM?

It is a protocol where you describe functions to the model, the model replies with a request to call one with specific arguments, your code runs it, and you return the result so the model can continue.

### Does the LLM execute the function itself?

No. For tools you define, your application executes the function. The model only decides which tool to call and with what arguments.

### What is an agent loop?

A loop that sends the conversation to the model, runs any tools it requests, appends the results and calls the model again until it stops asking for tools.

### How do tool use and MCP relate?

Tool use is the model capability of calling functions. MCP is a protocol that lets many apps discover and call tools hosted on servers, so you do not hand-write the tool wiring for each one.

### How should I handle a tool error?

Return a tool_result with is_error set to true and a short message, so the model can adapt or tell the user, instead of throwing and ending the loop.

