Agentic AI: Building AI Agents

Tool Use and Function Calling

Lesson 2 of 10

A model can only write text. Tool use, also called function calling, lets it ask your program to run a function and send back the result. This is the mechanism that turns a model into something that can act.

How it works

  1. You describe the available tools to the model.
  2. The user asks something.
  3. The model replies either with an answer, or with a request to call a tool with particular arguments.
  4. Your code runs the tool. The model never executes anything itself.
  5. You send the result back to the model.
  6. The model uses the result to answer, or asks for another tool.

Defining a tool

A tool definition has a name, a description and a schema for its inputs, written as JSON Schema.

{
  "name": "get_order_status",
  "description": "Look up the current status of a customer order. Use this
    whenever the customer asks where an order is or when it will arrive.
    Returns the status, the carrier and the expected delivery date.",
  "input_schema": {
    "type": "object",
    "properties": {
      "order_id": {
        "type": "string",
        "description": "The order number, for example A-10432"
      }
    },
    "required": ["order_id"]
  }
}

The description is a prompt

The model chooses tools by reading their descriptions. Write each one as you would explain the tool to a new colleague:

  • what it does,
  • when to use it and when not to,
  • what each parameter means, with an example,
  • what it returns.

Vague descriptions are the most common cause of a model choosing the wrong tool or passing the wrong arguments.

The exchange in code

This example uses a scripted stand-in for the model so that it runs in your browser. The stand-in returns the same kinds of message a real model would.

import json

# 1. the tools, as ordinary functions
ORDERS = {"A-10432": {"status": "in transit", "carrier": "Swiftline", "expected": "2026-10-14"}}

def get_order_status(order_id):
    return ORDERS.get(order_id, {"error": f"no order found with id {order_id}"})

TOOLS = {"get_order_status": get_order_status}

# 2. a scripted stand-in for the model
def fake_model(messages):
    last = messages[-1]
    if last["role"] == "user":
        return {"type": "tool_call", "name": "get_order_status", "arguments": {"order_id": "A-10432"}}
    result = json.loads(last["content"])
    return {"type": "text", "text": f"Your order is {result['status']} with {result['carrier']} "
                                    f"and should arrive on {result['expected']}."}

# 3. the exchange
messages = [{"role": "user", "content": "Where is my order A-10432?"}]
reply = fake_model(messages)
print("model asks for:", reply)

result = TOOLS[reply["name"]](**reply["arguments"])
print("tool returned: ", result)

messages.append({"role": "tool", "content": json.dumps(result)})
print("model answers: ", fake_model(messages)["text"])

With a real model, fake_model is replaced by an API call that includes the tool definitions. The structure of the exchange is the same.

Validate before you run

The arguments come from a model. Treat them as untrusted input.

def safe_call(tools, name, arguments, allowed_statuses=None):
    if name not in tools:
        return {"error": f"unknown tool: {name}"}
    order_id = arguments.get("order_id")
    if not isinstance(order_id, str) or not order_id.startswith("A-"):
        return {"error": "order_id must be a string such as A-10432"}
    return tools[name](order_id)

tools = {"get_order_status": lambda order_id: {"status": "in transit"}}
print(safe_call(tools, "get_order_status", {"order_id": "A-10432"}))
print(safe_call(tools, "get_order_status", {"order_id": 10432}))
print(safe_call(tools, "delete_everything", {}))

Return errors as results

When a tool fails, send the error back to the model as the result, with a message that explains what went wrong. The model can often correct its input and try again. An exception that crashes the program teaches it nothing.

Designing good tools

  • Give each tool one clear purpose, and a name that says what it does.
  • Avoid overlap. Two tools that do nearly the same thing confuse the choice.
  • Return only what is useful. A large result fills the context window. Summarise or let the model ask for detail.
  • Make inputs hard to get wrong. Prefer a fixed list of allowed values to free text.
  • Prefer a few capable tools to dozens of narrow ones.
  • Separate reading from changing. A tool that only reads is safe to call freely. A tool that changes something deserves more care, which a later lesson covers.

Common kinds of tool

Kind Examples
Information Web search, database queries, document retrieval
Computation A calculator, running code
Action Sending a message, creating a ticket, updating a record
Environment Reading and writing files, using a browser

Practice

Write a tool definition for search_products, which takes a search phrase and an optional maximum price. Make the description clear enough that a model would know when to use it.