Tools and Function Calling
Function calling lets a language model ask your program to run a function, and then use the result in its answer.
Why Tools?
A language model on its own cannot check today's dollar rate, look up an order in your database or do exact maths reliably. With tools, it can ask your code to do these things.
Important: the model does not run the function itself. It only replies with "please call get_order_status with order id 1045". Your code decides whether to run it, runs it, and sends back the result.
Step 1: Describe the Tool
You tell the model which tools exist using a name, a description and the parameters in JSON Schema format. In an OpenAI-compatible API it looks like this:
Example
tools = [{
"type": "function",
"function": {
"name": "get_order_status",
"description": "Get the delivery status of a customer's order by its order ID.",
"parameters": {
"type": "object",
"properties": {
"order_id": {"type": "string", "description": "The order number, like 1045"}
},
"required": ["order_id"],
},
},
}]
print(tools[0]["function"]["name"])get_order_status
A clear description is very important. The model uses it to decide when to use the tool.
Step 2: The Model Asks for a Tool
If the user writes "Where is my order 1045?", the model's reply contains a tool call instead of normal text. Its shape:
Example
import json
reply_message = {
"role": "assistant",
"content": None,
"tool_calls": [{
"id": "call_1",
"type": "function",
"function": {"name": "get_order_status", "arguments": '{"order_id": "1045"}'},
}],
}
call = reply_message["tool_calls"][0]
args = json.loads(call["function"]["arguments"])
print("Model wants:", call["function"]["name"], "with", args)Model wants: get_order_status with {'order_id': '1045'}Note that arguments is a JSON string, so you parse it with json.loads.
Step 3: Run the Tool and Send the Result
Your code runs the real function, then adds two messages to the conversation: the model's tool-call message, and a tool message with the result.
Example
import json
def get_order_status(order_id):
orders = {"1045": "Out for delivery in Gulshan-e-Iqbal, arriving today"}
return orders.get(order_id, "Order not found")
TOOLS = {"get_order_status": get_order_status}
tool_call = {"id": "call_1", "function": {"name": "get_order_status",
"arguments": '{"order_id": "1045"}'}}
name = tool_call["function"]["name"]
args = json.loads(tool_call["function"]["arguments"])
result = TOOLS[name](**args)
tool_message = {"role": "tool", "tool_call_id": tool_call["id"], "content": result}
print(tool_message){'role': 'tool', 'tool_call_id': 'call_1', 'content': 'Out for delivery in Gulshan-e-Iqbal, arriving today'}Step 4: The Model Answers
You send the conversation again, now including the tool result. The model writes a normal reply such as "Your order 1045 is out for delivery and should arrive today."
The Whole Flow
| Step | Who | What |
|---|---|---|
| 1 | You | Send messages + tool descriptions |
| 2 | Model | Replies with a tool call |
| 3 | Your code | Checks and runs the function |
| 4 | You | Send the result as a tool message |
| 5 | Model | Writes the final answer, or calls another tool |
Good Tool Design
- One clear job per tool:
get_order_status, notdo_everything. - Clear names and descriptions: the model only knows what you write.
- Check the arguments: the model can send wrong or missing values. Validate before running.
- Return short, useful results: huge outputs waste tokens.
- Separate reading and writing: tools that only read are safe; tools that change things (send, pay, delete) need extra checks.
Treat tool arguments from the model like input from a stranger on the internet. Validate them before you use them in a database query or file path.
Exercise
Exercise
In function calling, who actually runs the function?
