Skip to content

Function Calling with OpenAPI

Convert OpenAPI specifications to OpenAI function definitions for seamless tool integration.

Overview

Function calling enables LLMs to interact with external APIs and tools by generating structured arguments that match predefined schemas.

┌─────────────────────────────────────────────────────────────────────┐
│                    FUNCTION CALLING FLOW                             │
├─────────────────────────────────────────────────────────────────────┤
│                                                                      │
│  1. DEFINE TOOLS          2. LLM DECIDES         3. EXECUTE & LOOP  │
│  ┌─────────────┐          ┌─────────────┐        ┌─────────────┐    │
│  │  OpenAPI    │          │   Model     │        │  Execute    │    │
│  │   Spec      │─────────►│  Selects    │───────►│  Function   │    │
│  │             │  convert │   Tool +    │  args  │             │    │
│  │  /weather   │          │   Args      │        │  API Call   │    │
│  │  /search    │          │             │        │             │    │
│  └─────────────┘          └─────────────┘        └──────┬──────┘    │
│                                                         │           │
│                           ┌─────────────┐               │           │
│                           │   Return    │◄──────────────┘           │
│                           │   Result    │  tool result              │
│                           │   to LLM    │                           │
│                           └─────────────┘                           │
│                                                                      │
└─────────────────────────────────────────────────────────────────────┘

Converting OpenAPI to Function Definitions

OpenAPI Specification Example

yaml
# api_spec.yaml
openapi: 3.0.0
info:
  title: Weather API
  version: 1.0.0
paths:
  /weather:
    get:
      operationId: getWeather
      summary: Get current weather for a location
      parameters:
        - name: location
          in: query
          required: true
          schema:
            type: string
          description: City name or coordinates
        - name: unit
          in: query
          required: false
          schema:
            type: string
            enum: [celsius, fahrenheit]
            default: celsius
          description: Temperature unit
      responses:
        '200':
          description: Weather data
          content:
            application/json:
              schema:
                type: object
                properties:
                  temperature:
                    type: number
                  conditions:
                    type: string
                  humidity:
                    type: integer

Conversion Script

python
import yaml
import json
from typing import Any

def openapi_to_functions(spec_path: str) -> list[dict]:
    """Convert OpenAPI spec to OpenAI function definitions."""

    with open(spec_path) as f:
        spec = yaml.safe_load(f)

    functions = []

    for path, methods in spec.get('paths', {}).items():
        for method, details in methods.items():
            if method not in ['get', 'post', 'put', 'delete', 'patch']:
                continue

            function = {
                "type": "function",
                "function": {
                    "name": details.get('operationId', f"{method}_{path.replace('/', '_')}"),
                    "description": details.get('summary', ''),
                    "parameters": {
                        "type": "object",
                        "properties": {},
                        "required": []
                    }
                }
            }

            # Convert parameters
            for param in details.get('parameters', []):
                param_schema = param.get('schema', {})
                function["function"]["parameters"]["properties"][param['name']] = {
                    "type": param_schema.get('type', 'string'),
                    "description": param.get('description', ''),
                }

                # Add enum if present
                if 'enum' in param_schema:
                    function["function"]["parameters"]["properties"][param['name']]['enum'] = param_schema['enum']

                # Add to required if needed
                if param.get('required', False):
                    function["function"]["parameters"]["required"].append(param['name'])

            # Handle request body (for POST/PUT)
            if 'requestBody' in details:
                body_schema = details['requestBody'].get('content', {}).get('application/json', {}).get('schema', {})
                function["function"]["parameters"]["properties"]["body"] = body_schema
                function["function"]["parameters"]["required"].append("body")

            functions.append(function)

    return functions

# Usage
tools = openapi_to_functions('api_spec.yaml')
print(json.dumps(tools, indent=2))

Output Function Definition

python
tools = [
    {
        "type": "function",
        "function": {
            "name": "getWeather",
            "description": "Get current weather for a location",
            "parameters": {
                "type": "object",
                "properties": {
                    "location": {
                        "type": "string",
                        "description": "City name or coordinates"
                    },
                    "unit": {
                        "type": "string",
                        "description": "Temperature unit",
                        "enum": ["celsius", "fahrenheit"]
                    }
                },
                "required": ["location"]
            }
        }
    }
]

Using Functions with OpenAI

Basic Function Calling

python
from openai import OpenAI
import json

client = OpenAI()

def get_weather(location: str, unit: str = "celsius") -> dict:
    """Actual implementation that calls your weather API."""
    # Replace with real API call
    return {
        "location": location,
        "temperature": 22,
        "unit": unit,
        "conditions": "Sunny",
        "humidity": 45
    }

# Define the tool
tools = [
    {
        "type": "function",
        "function": {
            "name": "get_weather",
            "description": "Get current weather for a location",
            "parameters": {
                "type": "object",
                "properties": {
                    "location": {
                        "type": "string",
                        "description": "City name"
                    },
                    "unit": {
                        "type": "string",
                        "enum": ["celsius", "fahrenheit"],
                        "description": "Temperature unit"
                    }
                },
                "required": ["location"]
            }
        }
    }
]

# Initial request
messages = [
    {"role": "user", "content": "What's the weather like in Tokyo?"}
]

response = client.chat.completions.create(
    model="gpt-4o",
    messages=messages,
    tools=tools
)

# Check if model wants to call a function
if response.choices[0].message.tool_calls:
    tool_call = response.choices[0].message.tool_calls[0]

    # Parse arguments
    args = json.loads(tool_call.function.arguments)

    # Execute function
    result = get_weather(**args)

    # Add assistant message and tool result
    messages.append(response.choices[0].message)
    messages.append({
        "role": "tool",
        "tool_call_id": tool_call.id,
        "content": json.dumps(result)
    })

    # Get final response
    final_response = client.chat.completions.create(
        model="gpt-4o",
        messages=messages,
        tools=tools
    )

    print(final_response.choices[0].message.content)

Parallel Function Calls

GPT-4 can request multiple function calls simultaneously:

python
tools = [
    {
        "type": "function",
        "function": {
            "name": "get_weather",
            "description": "Get weather for a city",
            "parameters": {
                "type": "object",
                "properties": {
                    "city": {"type": "string"}
                },
                "required": ["city"]
            }
        }
    },
    {
        "type": "function",
        "function": {
            "name": "get_time",
            "description": "Get current time in a timezone",
            "parameters": {
                "type": "object",
                "properties": {
                    "timezone": {"type": "string"}
                },
                "required": ["timezone"]
            }
        }
    }
]

response = client.chat.completions.create(
    model="gpt-4o",
    messages=[
        {"role": "user", "content": "What's the weather and time in New York and London?"}
    ],
    tools=tools,
    parallel_tool_calls=True  # Enable parallel calls (default)
)

# Model may return multiple tool calls
for tool_call in response.choices[0].message.tool_calls:
    print(f"Function: {tool_call.function.name}")
    print(f"Arguments: {tool_call.function.arguments}")

Advanced Patterns

Tool Choice Control

python
# Let model decide (default)
response = client.chat.completions.create(
    model="gpt-4o",
    messages=[...],
    tools=tools,
    tool_choice="auto"
)

# Force a specific function
response = client.chat.completions.create(
    model="gpt-4o",
    messages=[...],
    tools=tools,
    tool_choice={"type": "function", "function": {"name": "get_weather"}}
)

# Force model to use ANY function
response = client.chat.completions.create(
    model="gpt-4o",
    messages=[...],
    tools=tools,
    tool_choice="required"
)

# Disable function calling for this turn
response = client.chat.completions.create(
    model="gpt-4o",
    messages=[...],
    tools=tools,
    tool_choice="none"
)

Strict Mode for Functions

python
tools = [
    {
        "type": "function",
        "function": {
            "name": "create_user",
            "description": "Create a new user",
            "strict": True,  # Enable strict mode
            "parameters": {
                "type": "object",
                "properties": {
                    "name": {"type": "string"},
                    "email": {"type": "string"},
                    "role": {
                        "type": "string",
                        "enum": ["admin", "user", "guest"]
                    }
                },
                "required": ["name", "email", "role"],
                "additionalProperties": False  # Required for strict mode
            }
        }
    }
]

Dynamic Tool Generation

python
def generate_crud_tools(entity_name: str, fields: dict) -> list:
    """Generate CRUD tools for any entity."""

    properties = {
        name: {"type": info["type"], "description": info.get("description", "")}
        for name, info in fields.items()
    }

    required = [name for name, info in fields.items() if info.get("required", False)]

    return [
        {
            "type": "function",
            "function": {
                "name": f"create_{entity_name}",
                "description": f"Create a new {entity_name}",
                "parameters": {
                    "type": "object",
                    "properties": properties,
                    "required": required
                }
            }
        },
        {
            "type": "function",
            "function": {
                "name": f"get_{entity_name}",
                "description": f"Get a {entity_name} by ID",
                "parameters": {
                    "type": "object",
                    "properties": {"id": {"type": "string"}},
                    "required": ["id"]
                }
            }
        },
        {
            "type": "function",
            "function": {
                "name": f"update_{entity_name}",
                "description": f"Update a {entity_name}",
                "parameters": {
                    "type": "object",
                    "properties": {"id": {"type": "string"}, **properties},
                    "required": ["id"]
                }
            }
        },
        {
            "type": "function",
            "function": {
                "name": f"delete_{entity_name}",
                "description": f"Delete a {entity_name}",
                "parameters": {
                    "type": "object",
                    "properties": {"id": {"type": "string"}},
                    "required": ["id"]
                }
            }
        }
    ]

# Generate tools for a "product" entity
product_tools = generate_crud_tools("product", {
    "name": {"type": "string", "required": True},
    "price": {"type": "number", "required": True},
    "description": {"type": "string", "required": False},
    "category": {"type": "string", "required": True}
})

Complete Function Calling Loop

python
import json
from openai import OpenAI

client = OpenAI()

# Tool implementations
TOOL_IMPLEMENTATIONS = {
    "get_weather": lambda **args: {"temp": 22, "conditions": "sunny"},
    "search_web": lambda **args: {"results": ["result1", "result2"]},
    "send_email": lambda **args: {"status": "sent", "id": "123"},
}

def run_conversation(user_message: str, tools: list, max_iterations: int = 10):
    """Run a complete conversation with tool use."""

    messages = [{"role": "user", "content": user_message}]

    for _ in range(max_iterations):
        response = client.chat.completions.create(
            model="gpt-4o",
            messages=messages,
            tools=tools
        )

        message = response.choices[0].message
        messages.append(message)

        # Check for tool calls
        if not message.tool_calls:
            # No more tool calls, return final response
            return message.content

        # Execute all tool calls
        for tool_call in message.tool_calls:
            func_name = tool_call.function.name
            func_args = json.loads(tool_call.function.arguments)

            # Execute the tool
            if func_name in TOOL_IMPLEMENTATIONS:
                result = TOOL_IMPLEMENTATIONS[func_name](**func_args)
            else:
                result = {"error": f"Unknown function: {func_name}"}

            # Add tool result to messages
            messages.append({
                "role": "tool",
                "tool_call_id": tool_call.id,
                "content": json.dumps(result)
            })

    return "Max iterations reached"

Error Handling

python
def safe_function_call(tool_call, implementations: dict):
    """Safely execute a function call with error handling."""

    func_name = tool_call.function.name

    try:
        # Parse arguments
        args = json.loads(tool_call.function.arguments)
    except json.JSONDecodeError as e:
        return {
            "error": "Invalid JSON arguments",
            "details": str(e)
        }

    # Check if function exists
    if func_name not in implementations:
        return {
            "error": f"Function '{func_name}' not found",
            "available_functions": list(implementations.keys())
        }

    try:
        # Execute function
        result = implementations[func_name](**args)
        return {"success": True, "result": result}
    except TypeError as e:
        return {
            "error": "Invalid arguments",
            "details": str(e)
        }
    except Exception as e:
        return {
            "error": "Function execution failed",
            "details": str(e)
        }

Best Practices

Function Descriptions

python
# BAD: Vague description
{
    "name": "process",
    "description": "Process data"
}

# GOOD: Detailed description
{
    "name": "process_customer_order",
    "description": "Process a customer order by validating inventory, calculating totals including tax and shipping, and creating a fulfillment request. Returns order confirmation with estimated delivery date."
}

Parameter Descriptions

python
# BAD: No descriptions
{
    "type": "object",
    "properties": {
        "date": {"type": "string"},
        "amount": {"type": "number"}
    }
}

# GOOD: Clear descriptions with examples
{
    "type": "object",
    "properties": {
        "date": {
            "type": "string",
            "description": "Transaction date in ISO 8601 format (e.g., '2024-01-15')"
        },
        "amount": {
            "type": "number",
            "description": "Transaction amount in USD (e.g., 99.99)"
        }
    }
}

See Also

Based on OpenAI Cookbook - Bain & OpenAI Collaboration