LLM Function Calling: Tool Use with OpenAI, Anthropic, and Gemini in Python

TL;DR — LLM function calling: tool use across providers. Meta-Intelligence: "Platform-proprietary implementations, but MCP driving standardization. Unified schema layer — single tool definition for multiple providers." FutureAGI: "Claude: parallel tool use, tool_choice (any, auto, none, specific), disable_parallel_tool_use. Structured outputs = hard-typed extension of function calling." OpenAI Docs: "parallel_tool_calls=false ensures zero or one tool. Strict mode for exact schema compliance." Agenta: "15+ providers with single API. Validation and retries handled. Switch models with minimal changes." DEV.to: "Build LLM agents that act, not just talk." Learn more with what is AI agent, build agent, tool loop, and switch provider.

Meta-Intelligence frames the landscape: "Current mainstream Function Calling implementations are still platform-proprietary, but Anthropic's open-source Model Context Protocol (MCP) is driving tool interface standardization. Establishing a unified schema definition layer — ensuring that a single tool definition can simultaneously generate compatible schemas for multiple providers."

FutureAGI describes the evolution: "Structured outputs are a hard-typed extension of function calling. Instead of letting the model decide which tool to call, you constrain the entire response to a specific schema."

Function Calling Architecture

flowchart TD subgraph Definition["Tool Definition"] Schema["JSON Schema
name, description
parameters with types
required fields"] end subgraph Providers["Provider APIs"] OpenAI["OpenAI
tools parameter
tool_choice: auto/required/none
parallel_tool_calls
strict mode"] Anthropic["Anthropic
tools parameter
tool_choice: any/auto/none/tool
disable_parallel_tool_use
input_schema"] Gemini["Google Gemini
function_declarations
function_calling_config
mode: AUTO/ANY/NONE"] end subgraph Flow["Function Calling Flow"] Define["1. Define Tools
JSON schema per tool"] Send["2. Send to LLM
messages + tools + tool_choice"] Decide["3. LLM Decides
which tool to call
generates arguments"] Execute["4. Execute Tool
run Python function
with arguments"] Return["5. Return Result
append tool result
to conversation"] Next["6. Next Turn
LLM processes result
may call more tools
or return final answer"] end subgraph Modes["tool_choice Modes"] Auto["auto
model decides
may or may not call"] Required["required/any
must call a tool"] None["none
no tool calls"] Specific["specific tool
force particular tool"] end subgraph Parallel["Parallel Calling"] MultiCall["Multiple tools per turn
execute concurrently
asyncio.gather"] Serial["Serial execution
parallel_tool_calls=false
disable_parallel_tool_use=true"] end Definition --> OpenAI Definition --> Anthropic Definition --> Gemini Define --> Send Send --> Decide Decide --> Execute Execute --> Return Return --> Next Auto --> Decide Required --> Decide None --> Decide Specific --> Decide MultiCall --> Execute Serial --> Execute

Provider Comparison

Feature OpenAI Anthropic Gemini
Parameter name tools tools function_declarations
Schema field parameters input_schema parameters
tool_choice auto 'auto' {type: 'auto'} mode: AUTO
tool_choice required 'required' {type: 'any'} mode: ANY
tool_choice none 'none' {type: 'none'} mode: NONE
Specific tool {type: 'function', function: {name}} {type: 'tool', name} function: {name}
Parallel calls parallel_tool_calls disable_parallel_tool_use Supported
Response format tool_calls array tool_use content blocks function_call parts
Result format role: 'tool' message tool_result content block function_response part

Implementation

from dataclasses import dataclass
from typing import Optional
from enum import Enum

class ToolChoice(Enum):
    AUTO = "auto"
    REQUIRED = "required"
    NONE = "none"
    SPECIFIC = "specific"

@dataclass
class LLMFunctionCallingGuide:
    """LLM function calling implementation guide."""

    def get_openai_function_calling(self) -> str:
        """OpenAI function calling."""
        return (
            "import json\n"
            "from openai import OpenAI\n"
            "\n"
            "client = OpenAI()\n"
            "\n"
            "TOOLS = [\n"
            "    {\n"
            "        'type': 'function',\n"
            "        'function': {\n"
            "            'name': 'get_weather',\n"
            "            'description':\n"
            "                'Get weather for'\n"
            "                ' a city',\n"
            "            'parameters': {\n"
            "                'type': 'object',\n"
            "                'properties': {\n"
            "                    'city': {\n"
            "                        'type': 'string',\n"
            "                        'description':\n"
            "                            'City name'\n"
            "                    },\n"
            "                    'unit': {\n"
            "                        'type': 'string',\n"
            "                        'enum': ['C', 'F']\n"
            "                    }\n"
            "                },\n"
            "                'required': ['city']\n"
            "            }\n"
            "        }\n"
            "    }\n"
            "]\n"
            "\n"
            "response = client.chat\\\n"
            "    .completions.create(\n"
            "    model='gpt-4o',\n"
            "    messages=[\n"
            "        {'role': 'user',\n"
            "         'content': 'Weather in NYC?'}\n"
            "    ],\n"
            "    tools=TOOLS,\n"
            "    tool_choice='auto',\n"
            "    # tool_choice='required',\n"
            "    # tool_choice='none',\n"
            "    # tool_choice={\n"
            "    #     'type': 'function',\n"
            "    #     'function': {\n"
            "    #         'name': 'get_weather'\n"
            "    #     }\n"
            "    # },\n"
            "    parallel_tool_calls=True,\n"
            ")\n"
            "\n"
            "msg = response.choices[0].message\n"
            "if msg.tool_calls:\n"
            "    for call in msg.tool_calls:\n"
            "        name = call.function.name\n"
            "        args = json.loads(\n"
            "            call.function.arguments)\n"
            "        print(f'Call: {name}')\n"
            "        print(f'Args: {args}')\n"
            "        # Execute function\n"
            "        result = get_weather(**args)\n"
            "        # Return result\n"
            "        messages = [\n"
            "            {'role': 'user',\n"
            "             'content': 'Weather?'}\n"
            "        ]\n"
            "        messages.append(msg)\n"
            "        messages.append({\n"
            "            'role': 'tool',\n"
            "            'tool_call_id':\n"
            "                call.id,\n"
            "            'content': str(result)\n"
            "        })\n"
            "        # Next turn\n"
            "        response = client.chat\\\n"
            "            .completions.create(\n"
            "            model='gpt-4o',\n"
            "            messages=messages,\n"
            "            tools=TOOLS)\n"
            "        print(response.choices[0]\n"
            "              .message.content)"
        )

    def get_anthropic_function_calling(self) -> str:
        """Anthropic Claude tool use."""
        return (
            "import anthropic\n"
            "\n"
            "client = anthropic.Anthropic()\n"
            "\n"
            "TOOLS = [\n"
            "    {\n"
            "        'name': 'get_weather',\n"
            "        'description':\n"
            "            'Get weather for a city',\n"
            "        'input_schema': {\n"
            "            'type': 'object',\n"
            "            'properties': {\n"
            "                'city': {\n"
            "                    'type': 'string',\n"
            "                    'description':\n"
            "                        'City name'\n"
            "                },\n"
            "                'unit': {\n"
            "                    'type': 'string',\n"
            "                    'enum': ['C', 'F']\n"
            "                }\n"
            "            },\n"
            "            'required': ['city']\n"
            "        }\n"
            "    }\n"
            "]\n"
            "\n"
            "response = client.messages.create(\n"
            "    model='claude-3-5-sonnet'\n"
            "        '-20241022',\n"
            "    max_tokens=1024,\n"
            "    tools=TOOLS,\n"
            "    tool_choice={\n"
            "        'type': 'auto'\n"
            "    },\n"
            "    # tool_choice={'type': 'any'},\n"
            "    # tool_choice={'type': 'none'},\n"
            "    # tool_choice={\n"
            "    #     'type': 'tool',\n"
            "    #     'name': 'get_weather'\n"
            "    # },\n"
            "    # disable_parallel_tool_use=True,\n"
            "    messages=[\n"
            "        {'role': 'user',\n"
            "         'content': 'Weather?'}\n"
            "    ]\n"
            ")\n"
            "\n"
            "for block in response.content:\n"
            "    if block.type == 'tool_use':\n"
            "        print(f'Tool: {block.name}')\n"
            "        print(f'Input: {block.input}')\n"
            "        # Execute function\n"
            "        result = get_weather(\n"
            "            **block.input)\n"
            "        # Return result\n"
            "        response = client\\\n"
            "            .messages.create(\n"
            "            model='claude-3-5-sonnet'\n"
            "                '-20241022',\n"
            "            max_tokens=1024,\n"
            "            tools=TOOLS,\n"
            "            messages=[\n"
            "                {'role': 'user',\n"
            "                 'content': 'Weather?'\n"
            "                },\n"
            "                response,\n"
            "                {\n"
            "                    'role': 'user',\n"
            "                    'content': [{\n"
            "                        'type':\n"
            "                            'tool_result',\n"
            "                        'tool_use_id':\n"
            "                            block.id,\n"
            "                        'content': str(\n"
            "                            result)\n"
            "                    }]\n"
            "                }\n"
            "            ]\n"
            "        )\n"
            "        print(response.content[0]\n"
            "              .text)"
        )

    def get_parallel_calling(self) -> str:
        """Parallel function calling with asyncio."""
        return (
            "import asyncio\n"
            "import json\n"
            "from openai import AsyncOpenAI\n"
            "\n"
            "client = AsyncOpenAI()\n"
            "\n"
            "async def run_parallel_tools():\n"
            "    response = await client\\\n"
            "        .chat.completions.create(\n"
            "        model='gpt-4o',\n"
            "        messages=[\n"
            "            {'role': 'user',\n"
            "             'content': 'Weather in'\n"
            "              ' NYC and LA?'}\n"
            "        ],\n"
            "        tools=TOOLS,\n"
            "        parallel_tool_calls=True\n"
            "    )\n"
            "    \n"
            "    msg = response\\\n"
            "        .choices[0].message\n"
            "    \n"
            "    if msg.tool_calls:\n"
            "        # Execute all tools\n"
            "        # concurrently\n"
            "        async def exec(call):\n"
            "            args = json.loads(\n"
            "                call.function\n"
            "                .arguments)\n"
            "            result = await \\\n"
            "                get_weather_async(\n"
            "                    **args)\n"
            "            return {\n"
            "                'role': 'tool',\n"
            "                'tool_call_id':\n"
            "                    call.id,\n"
            "                'content': str(\n"
            "                    result)\n"
            "            }\n"
            "        \n"
            "        results = await \\\n"
            "            asyncio.gather(*[\n"
            "                exec(c)\n"
            "                for c in msg.tool_calls\n"
            "            ])\n"
            "        # All results ready\n"
            "        # in parallel\n"
            "        print(results)\n"
            "\n"
            "asyncio.run(\n"
            "    run_parallel_tools())"
        )

    def get_structured_outputs(self) -> str:
        """Structured outputs — hard-typed function calling."""
        return (
            "from openai import OpenAI\n"
            "from pydantic import BaseModel\n"
            "\n"
            "client = OpenAI()\n"
            "\n"
            "class WeatherInfo(BaseModel):\n"
            "    city: str\n"
            "    temperature: float\n"
            "    unit: str\n"
            "    condition: str\n"
            "\n"
            "# Structured outputs constrain\n"
            "# entire response to schema\n"
            "response = client.chat\\\n"
            "    .completions.create(\n"
            "    model='gpt-4o',\n"
            "    messages=[\n"
            "        {'role': 'user',\n"
            "         'content': 'Weather in'\n"
            "          ' NYC as JSON'}\n"
            "    ],\n"
            "    response_format={\n"
            "        'type': 'json_schema',\n"
            "        'json_schema': {\n"
            "            'name': 'weather',\n"
            "            'schema': WeatherInfo\n"
            "                .model_json_schema(),\n"
            "            'strict': True\n"
            "        }\n"
            "    }\n"
            ")\n"
            "# Response guaranteed to\n"
            "# match WeatherInfo schema\n"
            "data = WeatherInfo\\\n"
            "    .model_validate_json(\n"
            "        response.choices[0]\n"
            "        .message.content)\n"
            "print(data.city)\n"
            "print(data.temperature)"
        )

    def get_litellm_unified(self) -> str:
        """LiteLLM unified function calling."""
        return (
            "import litellm\n"
            "\n"
            "TOOLS = [\n"
            "    {\n"
            "        'type': 'function',\n"
            "        'function': {\n"
            "            'name': 'get_weather',\n"
            "            'description':\n"
            "                'Get weather',\n"
            "            'parameters': {\n"
            "                'type': 'object',\n"
            "                'properties': {\n"
            "                    'city': {\n"
            "                        'type': 'string'\n"
            "                    }\n"
            "                },\n"
            "                'required': ['city']\n"
            "            }\n"
            "        }\n"
            "    }\n"
            "]\n"
            "\n"
            "# Same interface for all\n"
            "# providers — LiteLLM\n"
            "# translates formats\n"
            "\n"
            "# OpenAI\n"
            "response = litellm.completion(\n"
            "    model='openai/gpt-4o',\n"
            "    messages=[...],\n"
            "    tools=TOOLS,\n"
            "    tool_choice='auto')\n"
            "\n"
            "# Anthropic — same code\n"
            "response = litellm.completion(\n"
            "    model='anthropic/claude'\n"
            "        '-3-5-sonnet',\n"
            "    messages=[...],\n"
            "    tools=TOOLS,\n"
            "    tool_choice='auto')\n"
            "\n"
            "# Gemini — same code\n"
            "response = litellm.completion(\n"
            "    model='gemini/gemini-2.0'\n"
            "        '-flash',\n"
            "    messages=[...],\n"
            "    tools=TOOLS,\n"
            "    tool_choice='auto')\n"
            "\n"
            "# All return same format\n"
            "# regardless of provider"
        )

    def get_best_practices(self) -> dict:
        """Best practices."""
        return {
            "tool_descriptions": (
                "Write clear, specific "
                "descriptions. LLM uses "
                "these to decide which "
                "tool to call. Bad: "
                "'Search'. Good: 'Search "
                "the web for current "
                "information on a topic.'"
            ),
            "parameter_descriptions": (
                "Describe each parameter. "
                "Include type, format, "
                "and constraints. LLM "
                "generates arguments from "
                "these descriptions."
            ),
            "enum_constraints": (
                "Use enum for fixed "
                "options (e.g., unit: "
                "['C', 'F']). Reduces "
                "invalid arguments."
            ),
            "required_fields": (
                "Mark required parameters. "
                "Optional parameters give "
                "LLM flexibility but may "
                "lead to missing data."
            ),
            "strict_mode": (
                "OpenAI strict mode "
                "enforces exact schema "
                "compliance. Use for "
                "production reliability."
            ),
            "error_handling": (
                "Return errors as tool "
                "results. LLM adapts and "
                "may retry with corrected "
                "arguments."
            ),
        }

LLM Function Calling Checklist

  • [ ] Function calling lets LLMs invoke external tools by generating structured arguments from JSON schemas
  • [ ] Flow: define tools → send to LLM → LLM decides tool → generates arguments → execute → return result → next turn
  • [ ] OpenAI: tools parameter with {type: 'function', function: {name, description, parameters: JSON schema}}
  • [ ] OpenAI: tool_choice: 'auto' (model decides), 'required' (must call), 'none' (no tools), or specific tool object
  • [ ] OpenAI: parallel_tool_calls: true (default, multiple per turn) or false (one per turn)
  • [ ] OpenAI: strict mode enforces exact schema compliance
  • [ ] OpenAI: response includes tool_calls array with id, function name, and JSON arguments
  • [ ] OpenAI: return results as role='tool' messages with tool_call_id
  • [ ] Anthropic: tools parameter with {name, description, input_schema: JSON schema}
  • [ ] Anthropic: tool_choice: {type: 'auto'}, {type: 'any'}, {type: 'none'}, {type: 'tool', name: 'specific'}
  • [ ] Anthropic: disable_parallel_tool_use: true for serial execution
  • [ ] Anthropic: response includes tool_use content blocks with id, name, and input
  • [ ] Anthropic: return results as tool_result content blocks
  • [ ] Gemini: function_declarations parameter with name, description, parameters
  • [ ] Gemini: function_calling_config with mode: AUTO, ANY, NONE
  • [ ] Gemini: response includes function_call parts with name and args
  • [ ] Gemini: return results as function_response parts
  • [ ] Key difference: OpenAI uses parameters, Anthropic uses input_schema
  • [ ] Key difference: OpenAI uses tool_calls array, Anthropic uses content blocks, Gemini uses parts
  • [ ] Parallel function calling: LLM returns multiple tool_calls in one response
  • [ ] Parallel: execute all tools concurrently with asyncio.gather
  • [ ] Parallel: append all results as separate tool messages
  • [ ] Serial: parallel_tool_calls=false or disable_parallel_tool_use=true when order matters
  • [ ] Structured outputs: hard-typed extension of function calling — constrain entire response to schema
  • [ ] Structured outputs: response_format with json_schema and strict: true
  • [ ] Structured outputs: use Pydantic BaseModel for type-safe parsing
  • [ ] LiteLLM: unified interface for function calling across all providers
  • [ ] LiteLLM: same tools parameter and tool_choice for OpenAI, Anthropic, Gemini
  • [ ] LiteLLM: automatic format translation — all return same response format
  • [ ] MCP (Model Context Protocol): open standard for tool interface standardization
  • [ ] MCP: unified schema definition layer for multiple providers
  • [ ] Agenta: single API across 15+ providers with validation and retries
  • [ ] Tool descriptions: clear, specific — LLM uses these to decide which tool to call
  • [ ] Parameter descriptions: include type, format, constraints — LLM generates arguments from these
  • [ ] Enum constraints: use enum for fixed options to reduce invalid arguments
  • [ ] Required fields: mark required parameters, optional gives flexibility but may miss data
  • [ ] Error handling: return errors as tool results — LLM adapts and may retry
  • [ ] pip install openai for GPT, pip install anthropic for Claude
  • [ ] Platform-proprietary implementations but MCP driving standardization
  • [ ] Structured outputs constrain entire response, function calling lets model choose tool
  • [ ] Read what is AI agent for agent architecture
  • [ ] Read build agent for full implementation
  • [ ] Read tool loop for ReAct pattern
  • [ ] Read switch provider for multi-provider
  • [ ] Test: OpenAI function calling returns correct tool name and arguments
  • [ ] Test: Anthropic tool use returns correct tool_use blocks
  • [ ] Test: parallel calling executes multiple tools concurrently
  • [ ] Test: serial mode (parallel_tool_calls=false) executes one at a time
  • [ ] Test: structured outputs match schema exactly
  • [ ] Test: LiteLLM unified interface works across providers
  • [ ] Test: error handling returns errors as tool results for LLM adaptation
  • [ ] Document tools, schemas, tool_choice mode, parallel setting, and error handling

FAQ

What is LLM function calling?

LLM function calling lets models invoke external tools by generating structured arguments from JSON schemas. Meta-Intelligence: "Current mainstream Function Calling implementations are platform-proprietary, but Anthropic open-source MCP is driving tool interface standardization. Establishing a unified schema definition layer — ensuring that a single tool definition can simultaneously generate compatible schemas for multiple providers." FutureAGI: "Structured outputs are a hard-typed extension of function calling. Instead of letting the model decide which tool to call, you constrain the entire response to a specific schema." Agenta: "The library handles validation and retries. Works with over 15 LLM providers including OpenAI GPT, Anthropic Claude, Google Gemini, Ollama, DeepSeek using a single API." Function calling: (1) Define tools with JSON schema. (2) LLM decides which tool to call. (3) LLM generates arguments matching schema. (4) Your code executes the function. (5) Return result to LLM for next step.

How does OpenAI function calling work?

OpenAI uses the tools parameter with function schemas and tool_choice to control behavior. OpenAI Docs: "Set parallel_tool_calls to false to ensure exactly zero or one tool is called. If using a fine tuned model and the model calls multiple functions in one turn, strict mode will be disabled for those calls." FutureAGI: "Structured outputs are a hard-typed extension of function calling. Constrain the entire response to a specific schema." OpenAI tools API: (1) tools parameter: array of {type: 'function', function: {name, description, parameters: JSON schema}}. (2) tool_choice: 'auto' (model decides), 'required' (must call a tool), 'none' (no tools), or {type: 'function', function: {name: 'specific_tool'}}. (3) parallel_tool_calls: true (default, multiple tools per turn) or false (one tool per turn). (4) strict mode: enforces exact schema compliance. (5) Response includes tool_calls array with id, function name, and JSON arguments.

How does Anthropic Claude tool use differ from OpenAI?

Claude uses tool_use blocks with tool_choice options including any, auto, none, and specific tool. FutureAGI: "Claude supports parallel tool use, tool_choice (any, auto, none, or a specific tool), and disable_parallel_tool_use for strict serial execution." DEV.to: "pip install anthropic for Claude. Core differences in tool use between providers." Anthropic tool use: (1) tools parameter: array of {name, description, input_schema: JSON schema}. (2) tool_choice: {type: 'auto'} (model decides), {type: 'any'} (must use a tool), {type: 'tool', name: 'specific'}, {type: 'none'}. (3) disable_parallel_tool_use: true for serial execution. (4) Response includes tool_use content blocks with id, name, and input. (5) Return results as tool_result blocks. Key difference: Anthropic uses input_schema instead of parameters, content blocks instead of tool_calls array.

What is parallel function calling?

Parallel function calling lets the LLM call multiple tools in a single turn for faster execution. OpenAI Docs: "Set parallel_tool_calls to false to ensure exactly zero or one tool is called. By default, model may call multiple functions in one turn." FutureAGI: "Claude supports parallel tool use and disable_parallel_tool_use for strict serial execution." Parallel calling: (1) LLM returns multiple tool_calls in one response. (2) Execute all tools concurrently (asyncio.gather). (3) Append all results as separate tool messages. (4) LLM processes all results in next turn. Benefits: faster for independent tasks. Risks: dependencies between calls may require serial execution. Use parallel_tool_calls=false or disable_parallel_tool_use=true when order matters.

How do you standardize function calling across providers?

Use LiteLLM for unified interface or MCP for tool interface standardization. Meta-Intelligence: "Current implementations are platform-proprietary, but Anthropic MCP is driving tool interface standardization. Unified schema definition layer — single tool definition generates compatible schemas for multiple providers." Agenta: "Works with over 15 LLM providers including OpenAI, Anthropic, Gemini, Ollama, DeepSeek using a single API. Handles validation and retries. Switch models with minimal changes." DEV.to: "pip install openai for GPT, pip install anthropic for Claude. Build LLM agents that act, not just talk." Standardization: (1) LiteLLM: same completion() interface for all providers, automatic format translation. (2) MCP: open standard for tool definitions, provider-agnostic. (3) Agenta: single API across 15+ providers with validation. (4) Provider differences: OpenAI uses parameters, Anthropic uses input_schema. OpenAI uses tool_calls array, Anthropic uses content blocks. LiteLLM normalizes all to OpenAI format.


Want a self-hosted AI company brain that does all of this out of the box?
Book a demo →