AI Agents & Automation

Tool Use & Function Calling

Master function calling and tool use — the mechanism that lets LLMs interact with external APIs, databases, and systems.

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 response

OpenAI 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].text

Parallel 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

  1. Be specific about what the tool does
  2. Describe when to use it — help the model choose correctly
  3. Document parameters clearly — include examples and constraints
  4. 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

  1. Function calling bridges LLMs and the real world — from text to action
  2. Good descriptions are critical — the model chooses tools based on descriptions
  3. Handle errors gracefully — tools will fail, plan for it
  4. Security first — validate inputs, restrict access, require confirmation
  5. 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.