Back to Blog
6 min read

Mastering Function Calling in Azure OpenAI: A Deep Dive

Function calling is one of the most powerful features introduced in GPT-4 and GPT-3.5-Turbo. It allows models to reliably generate structured JSON for calling external functions. Today, I will provide a comprehensive guide to implementing function calling effectively.

Understanding Function Calling

Function calling enables:

  • Structured Output: Reliable JSON generation
  • Tool Integration: Connect AI to external APIs
  • Agent Behavior: Build autonomous AI agents
  • Data Extraction: Parse information from text
from openai import AzureOpenAI
import json

client = AzureOpenAI(
    api_key="your-api-key",
    api_version="2023-07-01-preview",
    azure_endpoint="https://your-resource.openai.azure.com"
)

# Basic function calling flow
# 1. Define functions
# 2. Send to model with user message
# 3. Model decides if/which function to call
# 4. Execute function with provided arguments
# 5. Send result back to model
# 6. Model generates final response

Defining Functions

Basic Function Definition

functions = [
    {
        "name": "get_current_weather",
        "description": "Get the current weather in a given location. Call this when the user asks about weather conditions.",
        "parameters": {
            "type": "object",
            "properties": {
                "location": {
                    "type": "string",
                    "description": "The city and state, e.g., San Francisco, CA"
                },
                "unit": {
                    "type": "string",
                    "enum": ["celsius", "fahrenheit"],
                    "description": "The temperature unit"
                }
            },
            "required": ["location"]
        }
    }
]

Complex Function Definitions

# E-commerce functions
ecommerce_functions = [
    {
        "name": "search_products",
        "description": "Search for products in the catalog. Use when user wants to find products, browse categories, or needs product recommendations.",
        "parameters": {
            "type": "object",
            "properties": {
                "query": {
                    "type": "string",
                    "description": "Search query for product name or description"
                },
                "filters": {
                    "type": "object",
                    "description": "Optional filters to narrow results",
                    "properties": {
                        "category": {
                            "type": "string",
                            "enum": ["electronics", "clothing", "home", "sports"]
                        },
                        "price_range": {
                            "type": "object",
                            "properties": {
                                "min": {"type": "number"},
                                "max": {"type": "number"}
                            }
                        },
                        "in_stock_only": {"type": "boolean"},
                        "min_rating": {"type": "number", "minimum": 1, "maximum": 5}
                    }
                },
                "sort_by": {
                    "type": "string",
                    "enum": ["relevance", "price_low", "price_high", "rating", "newest"]
                },
                "limit": {
                    "type": "integer",
                    "description": "Maximum number of results",
                    "default": 10
                }
            },
            "required": ["query"]
        }
    },
    {
        "name": "get_product_details",
        "description": "Get detailed information about a specific product including specs, reviews, and availability.",
        "parameters": {
            "type": "object",
            "properties": {
                "product_id": {
                    "type": "string",
                    "description": "The unique product identifier"
                },
                "include_reviews": {
                    "type": "boolean",
                    "description": "Whether to include customer reviews",
                    "default": False
                }
            },
            "required": ["product_id"]
        }
    },
    {
        "name": "add_to_cart",
        "description": "Add a product to the user's shopping cart",
        "parameters": {
            "type": "object",
            "properties": {
                "product_id": {"type": "string"},
                "quantity": {"type": "integer", "minimum": 1, "default": 1}
            },
            "required": ["product_id"]
        }
    },
    {
        "name": "check_order_status",
        "description": "Check the status of an existing order",
        "parameters": {
            "type": "object",
            "properties": {
                "order_id": {"type": "string"}
            },
            "required": ["order_id"]
        }
    }
]

Implementing the Function Calling Loop

class FunctionCallingAgent:
    def __init__(self, client, model, functions, system_prompt):
        self.client = client
        self.model = model
        self.functions = functions
        self.function_handlers = {}
        self.messages = [{"role": "system", "content": system_prompt}]

    def register_function(self, name, handler):
        """Register a function handler"""
        self.function_handlers[name] = handler

    def execute_function(self, name, arguments):
        """Execute a registered function"""
        if name not in self.function_handlers:
            return {"error": f"Function {name} not found"}

        try:
            result = self.function_handlers[name](**arguments)
            return result
        except Exception as e:
            return {"error": str(e)}

    def chat(self, user_message, max_function_calls=5):
        """Process user message with function calling"""
        self.messages.append({"role": "user", "content": user_message})

        function_call_count = 0

        while function_call_count < max_function_calls:
            response = self.client.chat.completions.create(
                model=self.model,
                messages=self.messages,
                functions=self.functions,
                function_call="auto"
            )

            message = response.choices[0].message

            # Check finish reason
            finish_reason = response.choices[0].finish_reason

            if finish_reason == "stop":
                # Model finished, return response
                self.messages.append(message)
                return message.content

            elif finish_reason == "function_call":
                # Model wants to call a function
                self.messages.append(message)

                function_name = message.function_call.name
                function_args = json.loads(message.function_call.arguments)

                print(f"Calling function: {function_name}")
                print(f"Arguments: {function_args}")

                # Execute function
                result = self.execute_function(function_name, function_args)

                # Add result to messages
                self.messages.append({
                    "role": "function",
                    "name": function_name,
                    "content": json.dumps(result)
                })

                function_call_count += 1

        return "Maximum function calls reached. Please try again with a simpler request."

# Usage example
agent = FunctionCallingAgent(
    client=client,
    model="gpt-4",
    functions=ecommerce_functions,
    system_prompt="""You are a helpful e-commerce assistant. Help users find products,
    check orders, and answer questions about our catalog. Always be helpful and provide
    relevant product recommendations."""
)

# Register function handlers
def search_products(query, filters=None, sort_by="relevance", limit=10):
    # Actual implementation would query a database
    return {
        "products": [
            {"id": "p1", "name": "Wireless Headphones", "price": 149.99},
            {"id": "p2", "name": "Bluetooth Speaker", "price": 79.99}
        ],
        "total": 2
    }

def get_product_details(product_id, include_reviews=False):
    return {
        "id": product_id,
        "name": "Wireless Headphones",
        "description": "Premium noise-canceling headphones",
        "price": 149.99,
        "in_stock": True,
        "reviews": [{"rating": 5, "text": "Great sound!"}] if include_reviews else None
    }

agent.register_function("search_products", search_products)
agent.register_function("get_product_details", get_product_details)

# Chat
response = agent.chat("I'm looking for wireless headphones under $200")
print(response)

Parallel Function Calls

GPT-4 can request multiple function calls in parallel:

def handle_parallel_functions(response):
    """Handle multiple function calls in one response"""
    message = response.choices[0].message

    if hasattr(message, 'tool_calls') and message.tool_calls:
        results = []

        for tool_call in message.tool_calls:
            function_name = tool_call.function.name
            function_args = json.loads(tool_call.function.arguments)

            # Execute function
            result = execute_function(function_name, function_args)

            results.append({
                "tool_call_id": tool_call.id,
                "role": "tool",
                "name": function_name,
                "content": json.dumps(result)
            })

        return results

    return None

Forcing Specific Functions

# Force the model to call a specific function
response = client.chat.completions.create(
    model="gpt-4",
    messages=messages,
    functions=functions,
    function_call={"name": "search_products"}  # Force this specific function
)

# Disable function calling for a specific request
response = client.chat.completions.create(
    model="gpt-4",
    messages=messages,
    functions=functions,
    function_call="none"  # Don't call any functions
)

Error Handling

def robust_function_execution(function_name, arguments, handlers):
    """Execute function with comprehensive error handling"""

    # Validate function exists
    if function_name not in handlers:
        return {
            "success": False,
            "error": f"Unknown function: {function_name}",
            "error_type": "function_not_found"
        }

    try:
        result = handlers[function_name](**arguments)
        return {
            "success": True,
            "result": result
        }
    except TypeError as e:
        return {
            "success": False,
            "error": f"Invalid arguments: {str(e)}",
            "error_type": "invalid_arguments"
        }
    except Exception as e:
        return {
            "success": False,
            "error": f"Function execution failed: {str(e)}",
            "error_type": "execution_error"
        }

def chat_with_error_handling(user_message):
    """Chat with graceful error handling"""
    messages = [
        {"role": "system", "content": "You are a helpful assistant."},
        {"role": "user", "content": user_message}
    ]

    try:
        response = client.chat.completions.create(
            model="gpt-4",
            messages=messages,
            functions=functions
        )
    except openai.RateLimitError:
        return "I'm currently experiencing high demand. Please try again in a moment."
    except openai.APIError as e:
        return f"An API error occurred. Please try again."

    message = response.choices[0].message

    if message.function_call:
        function_name = message.function_call.name

        try:
            arguments = json.loads(message.function_call.arguments)
        except json.JSONDecodeError:
            # Handle malformed JSON from model
            return "I had trouble processing that request. Could you rephrase?"

        result = robust_function_execution(function_name, arguments, handlers)

        if not result["success"]:
            # Provide error context back to model
            messages.append({
                "role": "function",
                "name": function_name,
                "content": json.dumps(result)
            })

            # Let model handle the error gracefully
            follow_up = client.chat.completions.create(
                model="gpt-4",
                messages=messages
            )
            return follow_up.choices[0].message.content

    return message.content

Function calling transforms GPT models into powerful agents that can interact with the real world. Tomorrow, I will cover Azure AI Studio in more detail.

Resources

Michael John Peña

Michael John Peña

Senior Data Engineer based in Sydney. Writing about data, cloud, and technology.