Tool Use & Function Calling
Function calling (also called tool use) allows LLMs to invoke external functions. Instead of generating a text response, the model outputs a structured function call that your code executes.
How Function Calling Works
1. You define available functions (name, description, parameters)
2. You send a message + function definitions to the LLM
3. The LLM decides if a function call is needed
4. If yes, the LLM returns a structured function call (not text)
5. Your code executes the function
6. You send the result back to the LLM
7. The LLM generates the final responseOpenAI Function Calling
Defining Tools
python
tools = [
{
"type": "function",
"function": {
"name": "get_stock_price",
"description": "Get the current stock price for a given ticker symbol",
"parameters": {
"type": "object",
"properties": {
"ticker": {
"type": "string",
"description": "Stock ticker symbol (e.g., AAPL, GOOGL)"
},
"currency": {
"type": "string",
"enum": ["USD", "EUR", "INR"],
"description": "Currency for the price"
}
},
"required": ["ticker"]
}
}
},
{
"type": "function",
"function": {
"name": "send_email",
"description": "Send an email to a recipient",
"parameters": {
"type": "object",
"properties": {
"to": {"type": "string", "description": "Email address"},
"subject": {"type": "string", "description": "Email subject"},
"body": {"type": "string", "description": "Email body"}
},
"required": ["to", "subject", "body"]
}
}
}
]Complete Flow
python
from openai import OpenAI
import json
client = OpenAI()
# Your actual function implementations
def get_stock_price(ticker, currency="USD"):
# In production, call a real stock API
prices = {"AAPL": 178.50, "GOOGL": 141.80, "MSFT": 378.90}
price = prices.get(ticker, 0)
return {"ticker": ticker, "price": price, "currency": currency}
def send_email(to, subject, body):
# In production, use an email service
print(f"Sending email to {to}: {subject}")
return {"status": "sent", "to": to}
# Map function names to implementations
function_map = {
"get_stock_price": get_stock_price,
"send_email": send_email
}
def chat_with_tools(user_message):
messages = [
{"role": "system", "content": "You are a helpful financial assistant."},
{"role": "user", "content": user_message}
]
# First API call
response = client.chat.completions.create(
model="gpt-4o",
messages=messages,
tools=tools,
tool_choice="auto" # Let the model decide
)
message = response.choices[0].message
# Check if the model wants to call functions
if message.tool_calls:
messages.append(message)
for tool_call in message.tool_calls:
func_name = tool_call.function.name
args = json.loads(tool_call.function.arguments)
# Execute the function
result = function_map[func_name](**args)
# Send result back
messages.append({
"role": "tool",
"tool_call_id": tool_call.id,
"content": json.dumps(result)
})
# Second API call with function results
final_response = client.chat.completions.create(
model="gpt-4o",
messages=messages
)
return final_response.choices[0].message.content
return message.content
# Usage
print(chat_with_tools("What's Apple's stock price?"))
# → Calls get_stock_price("AAPL")
# → "Apple's (AAPL) current stock price is $178.50 USD."Claude Function Calling
python
import anthropic
client = anthropic.Anthropic()
tools = [
{
"name": "get_weather",
"description": "Get current weather for a location",
"input_schema": {
"type": "object",
"properties": {
"location": {
"type": "string",
"description": "City name"
},
"units": {
"type": "string",
"enum": ["celsius", "fahrenheit"],
"description": "Temperature units"
}
},
"required": ["location"]
}
}
]
def chat_with_claude_tools(user_message):
response = client.messages.create(
model="claude-sonnet-4-20250514",
max_tokens=1024,
tools=tools,
messages=[{"role": "user", "content": user_message}]
)
# Check for tool use
for block in response.content:
if block.type == "tool_use":
tool_name = block.name
tool_input = block.input
# Execute tool
result = execute_tool(tool_name, tool_input)
# Send result back
final = client.messages.create(
model="claude-sonnet-4-20250514",
max_tokens=1024,
tools=tools,
messages=[
{"role": "user", "content": user_message},
{"role": "assistant", "content": response.content},
{
"role": "user",
"content": [{
"type": "tool_result",
"tool_use_id": block.id,
"content": json.dumps(result)
}]
}
]
)
return final.content[0].text
return response.content[0].textParallel Function Calls
LLMs can call multiple functions at once:
python
# User: "What's the weather in Tokyo and New York?"
# The model returns TWO tool calls simultaneously:
# tool_call_1: get_weather(location="Tokyo")
# tool_call_2: get_weather(location="New York")
# Execute both and send results back together
for tool_call in message.tool_calls:
func_name = tool_call.function.name
args = json.loads(tool_call.function.arguments)
result = function_map[func_name](**args)
messages.append({
"role": "tool",
"tool_call_id": tool_call.id,
"content": json.dumps(result)
})Forcing Tool Use
Always use a specific tool:
python
response = client.chat.completions.create(
model="gpt-4o",
messages=messages,
tools=tools,
tool_choice={"type": "function", "function": {"name": "get_stock_price"}}
)Never use tools (text only):
python
tool_choice="none"Let the model decide (default):
python
tool_choice="auto"Designing Good Tool Descriptions
The model decides which tool to use based on descriptions. Good descriptions = better tool selection.
python
# ❌ Bad description
{
"name": "search",
"description": "Search function"
}
# ✅ Good description
{
"name": "search_knowledge_base",
"description": "Search the company's internal knowledge base for product "
"documentation, FAQs, and troubleshooting guides. Use this "
"when the user asks about product features, pricing, or "
"technical support. Returns relevant document excerpts."
}Tips for Tool Descriptions
- Be specific about what the tool does
- Describe when to use it — help the model choose correctly
- Document parameters clearly — include examples and constraints
- Mention what it returns — so the model knows what to expect
Error Handling
python
def safe_tool_execution(func_name, args):
"""Execute a tool with error handling."""
try:
if func_name not in function_map:
return {"error": f"Unknown function: {func_name}"}
result = function_map[func_name](**args)
return result
except TypeError as e:
return {"error": f"Invalid arguments: {str(e)}"}
except Exception as e:
return {"error": f"Tool execution failed: {str(e)}"}Security Considerations
1. Validate All Arguments
python
def get_user_data(user_id):
# Validate the argument
if not isinstance(user_id, int) or user_id < 0:
return {"error": "Invalid user ID"}
# Sanitize for SQL injection
# Use parameterized queries, never string interpolation
return db.query("SELECT * FROM users WHERE id = %s", (user_id,))2. Restrict Available Tools
Only expose tools that are safe for the current user's permission level.
3. Require Confirmation for Dangerous Actions
python
REQUIRES_CONFIRMATION = ["send_email", "delete_file", "make_payment"]
if func_name in REQUIRES_CONFIRMATION:
confirmation = input(f"Allow {func_name}({args})? [y/n]: ")
if confirmation != "y":
return {"status": "cancelled by user"}4. Rate Limit Tool Calls
Prevent agents from making excessive API calls.
Key Takeaways
- Function calling bridges LLMs and the real world — from text to action
- Good descriptions are critical — the model chooses tools based on descriptions
- Handle errors gracefully — tools will fail, plan for it
- Security first — validate inputs, restrict access, require confirmation
- Parallel calls improve speed — LLMs can call multiple tools simultaneously
Next, we'll build fully autonomous agents that chain tool calls to complete complex tasks.