Menu

LLMs course · Lesson 7 of 12

Tool Calling (Function Calling) in LLMs, Explained with Code

How LLM tool calling works: describing tools, the model asking for a call, your code running it safely, and the loop behind AI agents, with runnable Python.

  • Intermediate
  • 5 min read
  • Updated Oct 2026
On this page
  1. How it works, step by step
  2. A complete example
  3. Writing good tool descriptions
  4. Safety rules
  5. From tool calling to agents and MCP
  6. Common mistakes
  7. Key takeaway

A language model can only produce text. Tool calling, also called function calling, lets it ask your application to run a function for it: look up an order, query a database, check the weather, create a ticket. The model decides when a tool is needed and with which arguments; your code decides whether to run it and actually runs it. This is how chat assistants check live data, and it is the basic building block of AI agents.

How it works, step by step

  1. You describe your tools to the model: a name, a description of when to use it, and the parameters it takes, usually as a JSON schema.
  2. The user asks something. The model reads the question and the tool descriptions.
  3. The model replies with a tool call instead of text, such as get_order_status with {"order_id": "A-1042"}.
  4. Your code checks and runs the tool, then sends the result back to the model as a new message.
  5. The model answers using the result, or asks for another tool.

The model never runs anything by itself. That is the most important point for safety: every permission check happens in your code.

A complete example

The example below implements the loop for a support assistant. A small stand-in function plays the model, returning exactly the kind of tool request a real model returns, so the code runs anywhere. Notice that the customer’s identity comes from the logged-in session, not from the model.

import json

ORDERS = {"A-1042": {"status": "shipped", "eta": "2026-11-03", "customer": "c-7"}}

def get_order_status(order_id: str, customer_id: str) -> dict:
    order = ORDERS.get(order_id)
    if order is None or order["customer"] != customer_id:      # permission check in code, not in the prompt
        return {"error": "order not found"}
    return {"status": order["status"], "eta": order["eta"]}

TOOLS = {
    "get_order_status": {
        "function": get_order_status,
        "description": "Look up the delivery status of one of the current customer's orders.",
        "parameters": {"order_id": "string, e.g. A-1042"},
    }
}

# Stand-in for the model: first it asks for a tool, then it answers using the tool result.
def call_model(messages):
    last = messages[-1]
    if last["role"] == "user":
        return {"tool_call": {"name": "get_order_status", "arguments": json.dumps({"order_id": "A-1042"})}}
    result = json.loads(last["content"])
    if "error" in result:
        return {"text": "I couldn't find that order on your account."}
    return {"text": f"Your order has {result['status']} and should arrive on {result['eta']}."}

def run(question, customer_id, max_steps=3):
    messages = [{"role": "user", "content": question}]
    for _ in range(max_steps):                                  # hard limit on steps
        reply = call_model(messages)
        if "text" in reply:
            return reply["text"]
        call = reply["tool_call"]
        tool = TOOLS.get(call["name"])
        if tool is None:
            return "Sorry, I can't do that."
        args = json.loads(call["arguments"])
        result = tool["function"](customer_id=customer_id, **args)   # identity comes from the session
        print(f"tool {call['name']}({args}) -> {result}")
        messages.append({"role": "tool", "content": json.dumps(result)})
    return "Sorry, I could not finish that."

print(run("Where is my order A-1042?", customer_id="c-7"))
print(run("Where is my order A-1042?", customer_id="c-9"))   # someone else's order
tool get_order_status({'order_id': 'A-1042'}) -> {'status': 'shipped', 'eta': '2026-11-03'}
Your order has shipped and should arrive on 2026-11-03.
tool get_order_status({'order_id': 'A-1042'}) -> {'error': 'order not found'}
I couldn't find that order on your account.

The same request from a different customer returns “not found”, because the tool checks ownership itself. Even if a user tricks the model into asking for someone else’s order, the code refuses.

Writing good tool descriptions

The model chooses tools from their names and descriptions, so write them like documentation for a new colleague:

  • One clear job per tool. get_order_status, not handle_order.
  • Say when to use it, and when not to.
  • Simple, typed parameters with examples, such as “string, e.g. A-1042”.
  • Useful results and errors. Return short, structured results, and error messages the model can act on, such as “order not found”.

Safety rules

  • Enforce permissions in code, using the real user’s identity from your session, never from the model’s arguments.
  • Validate every argument before running anything.
  • Limit steps, time and cost, so a confused model cannot loop forever.
  • Require confirmation for risky actions, such as refunds, payments or deleting data.
  • Treat tool results as untrusted: text returned from documents or websites can contain instructions that try to manipulate the model.

From tool calling to agents and MCP

When the model can call tools repeatedly, deciding each next step from the previous results, you have an AI agent: the loop above with more tools and more steps. See AI agents vs agentic AI. And when many applications need the same tools, the Model Context Protocol (MCP) provides a standard way to expose them once and reuse them everywhere. See RAG vs MCP.

Common mistakes

  1. Letting the model pass the user’s identity as a tool argument.
  2. Vague tool descriptions, so the model picks the wrong tool or invents arguments.
  3. Huge tool results that fill the context window; return only what is needed.
  4. No step limit, so a failing tool causes an endless loop.

Key takeaway

With tool calling, the model asks for a function call with arguments; your code validates it, checks permissions, runs it and returns the result. Clear tool descriptions make the model choose well, and safety lives in your code. This loop is the foundation of AI agents. Test yourself with the AI engineer interview questions.

By , Data Engineer and Founder of DataDank · Last reviewed Oct 2026 · The request and response shapes differ slightly between providers, but the loop is the same everywhere. The example uses a stand-in function in place of a real model so it runs anywhere; output shown is real.

Progress is saved in this browser only. No account needed.

Search
Filter by type