Skip to content
Back to Blog
Agentic AI7 min read

Function Calling vs Tool Use: What's the Difference and When to Use Each

Clarify the distinction between function calling and tool use in the context of large language models, covering terminology differences across providers, architectural patterns, implementation strategies, and guidance on when to use each approach for building AI applications.

The Terminology Confusion

If you have built LLM applications across different providers, you have encountered both "function calling" and "tool use" -- sometimes used interchangeably, sometimes referring to distinct concepts. The confusion exists because different providers chose different terminology for related but not identical features, and the ecosystem has not fully standardized.

Let us clarify the terms and their practical differences.

Definitions

Function Calling (OpenAI Terminology)

Function calling was introduced by OpenAI in June 2023. The term refers to the LLM's ability to output structured JSON that describes a function to call, including the function name and arguments. The model does not execute the function -- it generates the intent to call it.

from openai import OpenAI

client = OpenAI()

response = client.chat.completions.create(
    model="gpt-4o",
    messages=[
        {"role": "user", "content": "What's the weather in Tokyo?"}
    ],
    functions=[  # Original "functions" parameter
        {
            "name": "get_weather",
            "description": "Get weather for a city",
            "parameters": {
                "type": "object",
                "properties": {
                    "city": {"type": "string"},
                },
                "required": ["city"]
            }
        }
    ],
    function_call="auto"  # Original "function_call" parameter
)

# Model returns:
# function_call: { "name": "get_weather", "arguments": '{"city": "Tokyo"}' }

OpenAI later deprecated the functions parameter in favor of tools, but the community still uses "function calling" as the general term.

Tool Use (Anthropic Terminology)

Anthropic uses "tool use" to describe the same core capability: the model deciding to invoke an external tool and generating structured input for it. However, Anthropic's implementation uses a different message structure with explicit tool_use and tool_result content blocks.

import anthropic

client = anthropic.Anthropic()

response = client.messages.create(
    model="claude-sonnet-4-20250514",
    max_tokens=1024,
    tools=[  # "tools" parameter
        {
            "name": "get_weather",
            "description": "Get weather for a city",
            "input_schema": {
                "type": "object",
                "properties": {
                    "city": {"type": "string"},
                },
                "required": ["city"]
            }
        }
    ],
    messages=[
        {"role": "user", "content": "What's the weather in Tokyo?"}
    ]
)

# Model returns content blocks:
# [
#   {"type": "text", "text": "I'll check the weather for you."},
#   {"type": "tool_use", "id": "toolu_123", "name": "get_weather",
#    "input": {"city": "Tokyo"}}
# ]

The Unified "Tools" Standard

OpenAI has since migrated to a tools parameter that wraps functions, aligning terminology closer to Anthropic's:

# Modern OpenAI "tools" format
response = client.chat.completions.create(
    model="gpt-4o",
    messages=[...],
    tools=[
        {
            "type": "function",  # Tool type
            "function": {        # Function definition
                "name": "get_weather",
                "description": "Get weather for a city",
                "parameters": {
                    "type": "object",
                    "properties": {"city": {"type": "string"}},
                    "required": ["city"]
                }
            }
        }
    ],
    tool_choice="auto"
)

Architectural Differences

While the terminology is converging, there are genuine architectural differences in how providers implement the feature:

Message Structure

Aspect OpenAI Anthropic Google Gemini
Tool definition param tools[].function tools[] tools[].function_declarations
Model output format tool_calls array Content blocks (tool_use type) function_call in parts
Result return format tool role message tool_result content block function_response part
Parallel calls Yes (multiple tool_calls) Yes (multiple tool_use blocks) Yes (multiple function_calls)
Forcing a tool tool_choice: {type: "function", function: {name: "..."} } tool_choice: {type: "tool", name: "..."} tool_config.function_calling_config

Anthropic's Approach: Content Blocks

Anthropic's design treats tool use as just another content type alongside text. This means a single response can contain both text and tool calls interleaved:

# Claude can think out loud AND call tools in one response
response.content = [
    {"type": "text", "text": "Let me check the weather and your calendar."},
    {"type": "tool_use", "id": "toolu_1", "name": "get_weather",
     "input": {"city": "Tokyo"}},
    {"type": "tool_use", "id": "toolu_2", "name": "check_calendar",
     "input": {"date": "2026-01-22"}},
]

OpenAI's Approach: Separate Tool Call Array

OpenAI puts tool calls in a separate array on the message object:

# GPT-4o separates tool calls from content
message.content = "Let me check that for you."
message.tool_calls = [
    {"id": "call_1", "type": "function",
     "function": {"name": "get_weather", "arguments": '{"city": "Tokyo"}'}},
    {"id": "call_2", "type": "function",
     "function": {"name": "check_calendar", "arguments": '{"date": "2026-01-22"}'}},
]

Provider-Agnostic Tool Use

To build applications that work across providers, use an abstraction layer:

from dataclasses import dataclass
from abc import ABC, abstractmethod

@dataclass
class ToolCall:
    id: str
    name: str
    arguments: dict

@dataclass
class ToolResult:
    tool_call_id: str
    content: str
    is_error: bool = False

class LLMProvider(ABC):
    @abstractmethod
    async def generate(self, messages, tools, **kwargs):
        pass

    @abstractmethod
    def extract_tool_calls(self, response) -> list[ToolCall]:
        pass

    @abstractmethod
    def format_tool_results(self, results: list[ToolResult]) -> dict:
        pass


class AnthropicProvider(LLMProvider):
    async def generate(self, messages, tools, **kwargs):
        return await self.client.messages.create(
            model=kwargs.get("model", "claude-sonnet-4-20250514"),
            messages=messages,
            tools=tools,
            max_tokens=kwargs.get("max_tokens", 4096),
        )

    def extract_tool_calls(self, response) -> list[ToolCall]:
        return [
            ToolCall(id=b.id, name=b.name, arguments=b.input)
            for b in response.content
            if b.type == "tool_use"
        ]

    def format_tool_results(self, results: list[ToolResult]) -> dict:
        return {
            "role": "user",
            "content": [
                {
                    "type": "tool_result",
                    "tool_use_id": r.tool_call_id,
                    "content": r.content,
                    "is_error": r.is_error,
                }
                for r in results
            ],
        }


class OpenAIProvider(LLMProvider):
    async def generate(self, messages, tools, **kwargs):
        # Convert tool format from Anthropic-style to OpenAI-style
        oai_tools = [
            {
                "type": "function",
                "function": {
                    "name": t["name"],
                    "description": t["description"],
                    "parameters": t["input_schema"],
                }
            }
            for t in tools
        ]
        return await self.client.chat.completions.create(
            model=kwargs.get("model", "gpt-4o"),
            messages=messages,
            tools=oai_tools,
        )

    def extract_tool_calls(self, response) -> list[ToolCall]:
        msg = response.choices[0].message
        if not msg.tool_calls:
            return []
        return [
            ToolCall(
                id=tc.id,
                name=tc.function.name,
                arguments=json.loads(tc.function.arguments),
            )
            for tc in msg.tool_calls
        ]

    def format_tool_results(self, results: list[ToolResult]) -> list[dict]:
        return [
            {
                "role": "tool",
                "tool_call_id": r.tool_call_id,
                "content": r.content,
            }
            for r in results
        ]

When to Use Which Pattern

Use "Simple" Tool Calling When:

  • You have a fixed set of tools that rarely changes
  • Each tool call is independent (no chaining needed)
  • The application controls which tools are available
  • You want a single LLM round-trip
# Example: Extracting structured data using tool calling
# (force a specific tool to get guaranteed structured output)
response = client.messages.create(
    model="claude-sonnet-4-20250514",
    tools=[extraction_tool],
    tool_choice={"type": "tool", "name": "extract_data"},
    messages=[{"role": "user", "content": document_text}],
)

Use Agentic Tool Use When:

  • The LLM needs to decide which tools to call dynamically
  • Tool calls may be chained (output of one feeds into another)
  • The number of tool calls is not predetermined
  • Complex multi-step reasoning is required
# Example: Agent that researches, calculates, and reports
async def research_agent(query: str):
    messages = [{"role": "user", "content": query}]

    while True:
        response = await client.messages.create(
            model="claude-sonnet-4-20250514",
            tools=[search_tool, calculator_tool, chart_tool, report_tool],
            tool_choice={"type": "auto"},  # Model decides
            messages=messages,
        )

        if response.stop_reason == "end_turn":
            return extract_text(response)

        # Model autonomously chains tools as needed
        messages.append({"role": "assistant", "content": response.content})
        results = await execute_tools(response)
        messages.append({"role": "user", "content": results})

Use MCP When:

  • Tools need to be shared across multiple applications
  • Third-party tool providers want a standard interface
  • Tools require their own lifecycle management (connections, auth)
  • You want to decouple tool implementation from AI application code

Common Patterns Across Both Approaches

Forced Tool Use for Structured Output

All providers support forcing a specific tool, which guarantees structured output:

# Anthropic
tool_choice={"type": "tool", "name": "extract_entities"}

# OpenAI
tool_choice={"type": "function", "function": {"name": "extract_entities"}}

# Google
tool_config={"function_calling_config": {"mode": "ANY",
             "allowed_function_names": ["extract_entities"]}}

Disabling Tool Use

Sometimes you want the model to respond with text only, even when tools are defined:

# Anthropic: tool_choice={"type": "none"}
# OpenAI: tool_choice="none"
# Google: tool_config={"function_calling_config": {"mode": "NONE"}}

Summary: Function Calling vs Tool Use

Aspect Function Calling Tool Use
Origin OpenAI (June 2023) Anthropic (April 2024)
Core concept Model generates function call intent Model generates tool invocation request
Practical difference Minimal (same underlying capability) Minimal (same underlying capability)
Key architectural difference Separate tool_calls array Content blocks alongside text
Modern naming trend Converging on "tools" Converging on "tools"

The bottom line: "function calling" and "tool use" describe the same fundamental capability -- the model requesting execution of external code. The terms originated from different providers and are now converging around the "tools" terminology. When building applications, focus on the architectural patterns (simple extraction vs agentic loop vs MCP) rather than the terminology.

Share this article
N

NYC News

Expert insights on AI voice agents and customer communication automation.

Try CallSphere AI Voice Agents

See how AI voice agents work for your industry. Live demo available -- no signup required.