Function calling lets an LLM decide which external tool to use and what arguments to pass — but the application code controls which tools exist, which are allowed, and what happens with the result. The LLM never executes code directly. It produces a structured request; your code validates and executes it.
You ask a chatbot: "What's the weather in London and what is 15 times 7 plus 3?"
The LLM doesn't know the weather. It can't reliably do arithmetic. But it knows which tool to call and what arguments to pass. That's function calling.
This tutorial explains the complete function calling loop — from user request through tool selection, validation, execution, and back to a final answer. You'll learn with safe calculator and weather examples, then understand the security model that makes this practical.
This tutorial builds on the How AI Coding Agents Work article and connects to MCP vs APIs and Structured AI Outputs.
1. The Complete Loop
Function calling follows a strict cycle. The LLM never breaks the loop by executing code directly.
Each step has a clear owner:
| Step | Owner | What Happens |
|---|---|---|
| 1. User asks | User | Natural language request |
| 2. LLM decides | LLM | Selects tool + generates arguments as JSON |
| 3. Validate | Your code | Permission check + argument validation |
| 4. Execute | Your code | Runs the actual function |
| 5. Return result | Your code | Sends result back to LLM as context |
| 6. LLM answers | LLM | Generates natural language answer using tool result |
2. Tool Definitions
Before the LLM can call a tool, you define what tools exist. Each definition includes:
{
"name": "calculator",
"description": "Perform mathematical calculations",
"parameters": {
"type": "object",
"properties": {
"expression": {
"type": "string",
"description": "Math expression to evaluate"
}
},
"required": ["expression"]
}
}
The tool definition is essentially a JSON schema. The LLM reads this definition and generates arguments matching the schema when it decides to call the tool.
What the LLM Sees
You send the LLM a system message listing all available tools. The LLM doesn't execute them — it just knows they exist and what arguments they accept.
You have access to the following tools:
calculator(expression) — Evaluate math
weather(city, units) — Get current weather
When you need a tool, respond with:
{"tool_calls": [{"name": "...", "arguments": {...}}]}
3. What the LLM Outputs
When the LLM decides to use a tool, it produces structured JSON — not executable code:
{
"tool_calls": [
{
"name": "calculator",
"arguments": {
"expression": "25 * 17 + 3"
}
}
]
}
Your code receives this JSON, validates it, and executes the calculator function with the argument "25 * 17 + 3". The LLM never touches the calculator code.
Multiple Tool Calls
The LLM can request multiple tools in a single response:
{"name": "weather", "arguments": {"city": "London"}},
{"name": "calculator", "arguments": {"expression": "100 / 7"}}
]
Your code executes both tools and returns both results. The LLM then combines them into a natural language answer.
4. Validation and Security
Never trust the LLM's tool call output blindly. Always validate before execution.
Layer 1: Tool Allowlist
Which tools exist in your system? The LLM can only call tools you've defined.
Layer 2: Permission Check
Which tools is this request allowed to use? A customer-facing app might only allow calculator and weather — not file_reader or database_query.
if tool_name not in policy.allowed_tools:
return False, "Tool not allowed"
if tool_name in policy.blocked_tools:
return False, "Tool explicitly blocked"
return True, "Allowed"
Layer 3: Argument Validation
Even allowed tools can receive dangerous arguments. Validate before executing:
| Tool | Dangerous Argument | Blocked By |
|---|---|---|
| calculator | __import__('os').system('rm -rf /') |
Forbidden pattern blocklist |
| database_query | DROP TABLE users |
SQL write-operation blocklist |
| file_reader | /etc/passwd |
Sensitive path blocklist |
Layer 4: Human Confirmation
For high-risk operations, require human approval before execution. The tool call pauses until a human clicks "Approve."
5. Calculator Example: Complete Walkthrough
Let's trace a complete function call for "What is 25 times 17 plus 3?"
user: "What is 25 * 17 + 3?"
# Step 2: LLM responds with tool call
assistant: {
"tool_calls": [{
"name": "calculator",
"arguments": {"expression": "25 * 17 + 3"}
}]
}
# Step 3: Your code validates
Permission check: calculator → ✅ Allowed
Argument check: expression="25 * 17 + 3" → ✅ Safe
# Step 4: Your code executes
result = calculator(expression="25 * 17 + 3")
result = {"result": 428, "expression": "25 * 17 + 3"}
# Step 5: Result sent back to LLM
tool_result: {"result": 428}
# Step 6: LLM generates answer
assistant: "25 × 17 + 3 = 428"
The LLM chose the tool and arguments. Your code validated, executed, and returned the result. The LLM then wrote the final answer.
6. Weather Example
The same loop works for API calls:
assistant: {
"tool_calls": [{
"name": "weather",
"arguments": {"city": "London", "units": "celsius"}
}]
}
result: {"city": "London", "temp": 15, "condition": "Cloudy"}
assistant: "It's currently 15°C and Cloudy in London."
In production, the weather function would call a real weather API. The pattern is identical — the LLM provides the arguments, your code makes the API call.
7. Function Calling vs MCP
Function calling and MCP (Model Context Protocol) solve related but different problems:
| Aspect | Function Calling | MCP |
|---|---|---|
| What it is | API-level feature from LLM providers | Open protocol for tool integration |
| Tool definition | JSON schema per request | Server advertises tools dynamically |
| Execution | Your code handles each call | MCP server handles execution |
| Discovery | Static (you define tools) | Dynamic (server publishes tools) |
| Multi-tool | Per-provider format | Standardized across providers |
| Best for | Simple, provider-specific tools | Portable, multi-server integrations |
8. Common Mistakes
| Mistake | Risk | Fix |
|---|---|---|
| Executing tool calls without validation | Malicious or malformed arguments | Always validate before execution |
| No tool allowlist | LLM invents tools that don't exist | Define explicit tool list |
| Giving LLM all tools always | Unnecessary access to sensitive tools | Least-privilege per request |
| Trusting LLM arguments blindly | SQL injection, path traversal | Validate every argument |
| No retry on malformed output | Single bad output = failure | Retry with error feedback |
9. FAQ
Continue Learning
The agent loop: plan, execute, observe Structured AI Outputs
Getting reliable JSON from LLMs MCP vs APIs
What's the difference and why it matters MCP Security Checklist
25-point security checklist for tool integration Build Your First LLM Application
Complete beginner tutorial with text summarizer JSON Formatter
Format and debug tool call JSON