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: integerConversion 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)"
}
}
}