Search Tutorials


Azure AI Foundry Tutorials - Function Calling Agent | JavaInUse

Azure AI Foundry Tutorials - Function Calling Agent

Beyond Information Retrieval - Taking Actions

In previous tutorial we had created an agent with Bing web search capability. That agent could retrieve real-time information from the web, which solved the knowledge cutoff problem. But it could only provide information - it could not take any actions. Imagine you are building a customer service agent for an airline. A customer asks:
"I need to change my seat to 12A on my flight tomorrow, confirmation number ABC123"
An information-only agent would respond:
"To change your seat, please visit our website or call our customer service number..."

Information vs Action Agent
But what if the agent could actually change the seat for the customer? This is where Function Calling comes in.
Information vs Action Agent
Function Calling allows AI agents to execute custom Python functions, enabling them to take real actions like:
  • Look up data from databases
  • Modify records in systems
  • Call external APIs
  • Send emails or notifications
  • Process transactions
  • Interact with business logic
This transforms agents from passive information providers into active assistants that can accomplish tasks on behalf of users.

Video

This tutorial is explained in the below Youtube Video.

Azure AI Agents - Table of Contents

Jump between Foundry tutorials, SDK builds, and grounded agent examples.

What is Function Calling?

Function calling (also called tool calling) is a capability that allows LLMs to:
  1. Recognize when they need to execute a function to accomplish a task
  2. Determine which function to call based on the user's request
  3. Extract the appropriate parameters from the conversation
  4. Request the function execution with those parameters
  5. Use the function's return value to formulate a response
The key insight is that the LLM does not actually execute the function - instead, it requests that your code execute it. This keeps the LLM safe (it cannot run arbitrary code) while giving it the power to orchestrate complex workflows.
Function Calling Flow

Use Case: Airline Booking Assistant

Let us build a realistic airline booking assistant that can help customers with their flight bookings. The agent will have access to these functions:
  • check_booking(confirmation_number) - Look up flight details using confirmation number
  • modify_seat(booking_id, new_seat) - Change seat assignments
  • process_refund(booking_id, amount) - Issue refunds for cancellations
  • send_email(customer_email, message) - Send confirmation emails
A customer could interact with this agent like:
Customer: "Can you check my booking ABC123?"
Agent: [Calls check_booking("ABC123")]
Agent: "Your flight is from New York to London on June 15th, seat 14B. How can I help you?"

Customer: "I'd like to change my seat to 12A"
Agent: [Calls modify_seat("ABC123", "12A")]
Agent: "Done! I've changed your seat to 12A and sent you a confirmation email."

Airline Agent Use Case

How Function Calling Works

Here is the complete flow when a user makes a request that requires function calling: Step 1: User makes a request
User: "Change my seat to 12A for booking ABC123"
Step 2: Agent analyzes the request and decides a function is needed
Agent thinks: "The user wants to modify a seat. I should use the modify_seat function."
Step 3: Agent extracts parameters from the conversation
Agent identifies:
- booking_id: "ABC123"
- new_seat: "12A"
Step 4: Agent requests function execution (run status becomes "requires_action")
Agent requests: modify_seat(booking_id="ABC123", new_seat="12A")
Step 5: Your code executes the function and returns the result
Function returns: {"success": true, "message": "Seat changed to 12A"}
Step 6: Agent uses the result to formulate a natural language response
Agent responds: "I've successfully changed your seat to 12A. You'll receive a confirmation email shortly."
This entire orchestration happens automatically once you define the functions!

Prerequisites

Before implementing function calling, make sure you have:
  • Completed the previous tutorial on Azure AI Foundry SDK
  • An Azure AI Foundry project with a deployed GPT-4o model
  • Python environment with azure-ai-agents package installed
  • Understanding of Python functions and dictionaries

Implementation - Defining Functions

Let us start by implementing the Python functions that our agent will call. These functions simulate an airline booking system. Create a new file called airline_functions.py:
# airline_functions.py
"""
Simulated airline booking system functions.
In a real system, these would connect to actual databases and APIs.
"""

# Simulated database of bookings
bookings_db = {
    "ABC123": {
        "booking_id": "ABC123",
        "passenger_name": "John Doe",
        "flight_number": "AA101",
        "route": "New York (JFK) to London (LHR)",
        "date": "2026-06-15",
        "seat": "14B",
        "status": "confirmed",
        "email": "john.doe@email.com"
    },
    "XYZ789": {
        "booking_id": "XYZ789",
        "passenger_name": "Jane Smith",
        "flight_number": "AA202",
        "route": "Los Angeles (LAX) to Tokyo (NRT)",
        "date": "2026-07-20",
        "seat": "23A",
        "status": "confirmed",
        "email": "jane.smith@email.com"
    }
}


def check_booking(confirmation_number: str) -> dict:
    """
    Look up flight booking details by confirmation number.
    
    Args:
        confirmation_number: The booking confirmation number
        
    Returns:
        Dictionary with booking details or error message
    """
    print(f"[FUNCTION CALLED] check_booking('{confirmation_number}')")
    
    if confirmation_number in bookings_db:
        booking = bookings_db[confirmation_number]
        return {
            "success": True,
            "booking": booking
        }
    else:
        return {
            "success": False,
            "error": f"No booking found with confirmation number {confirmation_number}"
        }


def modify_seat(booking_id: str, new_seat: str) -> dict:
    """
    Change the seat assignment for a booking.
    
    Args:
        booking_id: The booking confirmation number
        new_seat: The new seat number (e.g., "12A")
        
    Returns:
        Dictionary with success status and message
    """
    print(f"[FUNCTION CALLED] modify_seat('{booking_id}', '{new_seat}')")
    
    if booking_id not in bookings_db:
        return {
            "success": False,
            "error": f"No booking found with ID {booking_id}"
        }
    
    # Validate seat format (basic validation)
    if not new_seat or len(new_seat) < 2:
        return {
            "success": False,
            "error": "Invalid seat number format"
        }
    
    # Update the seat
    old_seat = bookings_db[booking_id]["seat"]
    bookings_db[booking_id]["seat"] = new_seat
    
    return {
        "success": True,
        "message": f"Seat changed from {old_seat} to {new_seat}",
        "new_seat": new_seat
    }


def process_refund(booking_id: str, amount: float) -> dict:
    """
    Process a refund for a booking cancellation.
    
    Args:
        booking_id: The booking confirmation number
        amount: The refund amount in USD
        
    Returns:
        Dictionary with refund confirmation
    """
    print(f"[FUNCTION CALLED] process_refund('{booking_id}', {amount})")
    
    if booking_id not in bookings_db:
        return {
            "success": False,
            "error": f"No booking found with ID {booking_id}"
        }
    
    # Update booking status
    bookings_db[booking_id]["status"] = "refunded"
    
    return {
        "success": True,
        "message": f"Refund of ${amount:.2f} processed successfully",
        "refund_amount": amount,
        "booking_id": booking_id
    }


def send_email(customer_email: str, message: str) -> dict:
    """
    Send an email to the customer.
    
    Args:
        customer_email: Customer's email address
        message: Email message content
        
    Returns:
        Dictionary with send confirmation
    """
    print(f"[FUNCTION CALLED] send_email('{customer_email}', message_length={len(message)})")
    
    # In a real system, this would integrate with an email service
    # For now, we just simulate it
    
    print(f"  EMAIL TO: {customer_email}")
    print(f"  SUBJECT: Booking Update")
    print(f"  MESSAGE: {message}")
    
    return {
        "success": True,
        "message": f"Email sent to {customer_email}",
        "email": customer_email
    }


Let us break down what we just created:

1. Simulated Database

bookings_db = {
    "ABC123": {...},
    "XYZ789": {...}
}
This dictionary simulates a database of flight bookings. In a real system, this would be an actual database.

2. Python Functions

Each function (check_booking, modify_seat, process_refund, send_email) is a regular Python function that performs an action and returns a dictionary with the result.

3. Function Definitions

The llm does not know anything about the python functions that we have defined above. So how do let the llm know which function calls are at its disposal. For this we make use of function_definitions. The function_definitions list describes each function in a format the LLM can understand. This includes:
  • name - Function name
  • description - What the function does (the LLM uses this to decide when to call it)
  • parameters - What arguments the function accepts, their types, and descriptions
  • required - Which parameters are mandatory
In the main agent code later we will we make use of
functions = FunctionTool(function_definitions)
FunctionTool will automatically create a function definition like this from the python code on the fly and this is then passed to the llm. So now the llm knows which function it can call for a specific scenario.
{
    "type": "function",
    "function": {
        "name": "check_booking",  # <- From function.__name__
        "description": "Look up flight booking details by confirmation number.",  # <- From docstring (first line)
        "parameters": {
            "type": "object",
            "properties": {
                "confirmation_number": {
                    "type": "string",  # <- From type hint: str
                    "description": "The booking confirmation number"  # <- From Args section in docstring
                }
            },
            "required": ["confirmation_number"]  # <- Inferred from function signature (no default value)
        }
    }
}
So similar to above below function definitions will be created by functionTool and sent to llm.
# Function definitions for the agent (in OpenAI function calling format)
# LLM metadata
function_definitions = [
    {
        "type": "function",
        "function": {
            "name": "check_booking",
            "description": "Look up flight booking details using the confirmation number",
            "parameters": {
                "type": "object",
                "properties": {
                    "confirmation_number": {
                        "type": "string",
                        "description": "The booking confirmation number (e.g., ABC123)"
                    }
                },
                "required": ["confirmation_number"]
            }
        }
    },
    {
        "type": "function",
        "function": {
            "name": "modify_seat",
            "description": "Change the seat assignment for an existing booking",
            "parameters": {
                "type": "object",
                "properties": {
                    "booking_id": {
                        "type": "string",
                        "description": "The booking confirmation number"
                    },
                    "new_seat": {
                        "type": "string",
                        "description": "The new seat number (e.g., 12A, 23B)"
                    }
                },
                "required": ["booking_id", "new_seat"]
            }
        }
    },
    {
        "type": "function",
        "function": {
            "name": "process_refund",
            "description": "Process a refund for a cancelled booking",
            "parameters": {
                "type": "object",
                "properties": {
                    "booking_id": {
                        "type": "string",
                        "description": "The booking confirmation number"
                    },
                    "amount": {
                        "type": "number",
                        "description": "The refund amount in USD"
                    }
                },
                "required": ["booking_id", "amount"]
            }
        }
    },
    {
        "type": "function",
        "function": {
            "name": "send_email",
            "description": "Send an email notification to the customer",
            "parameters": {
                "type": "object",
                "properties": {
                    "customer_email": {
                        "type": "string",
                        "description": "Customer's email address"
                    },
                    "message": {
                        "type": "string",
                        "description": "The email message content"
                    }
                },
                "required": ["customer_email", "message"]
            }
        }
    }
]

4. Function Mapping

Now the llm only knows the function definition name. So it returns this function definition name when it needs to call one. But the name of the function definition and the actual python code function may not always be the same. So we create a mapping of function definition and python definition. When function definition is returned by llm, indicating to execute a python function. We check this mapping to get the corresponding python function and execute it.
# Mapping of function names to actual Python functions
available_functions = {
    "check_booking": check_booking,
    "modify_seat": modify_seat,
    "process_refund": process_refund,
    "send_email": send_email
}

This maps function names (strings) to actual Python function objects, so we can call them dynamically. Add this to the airline_function.py.
In previous tutorial we had created an azure foundry project. We will be making use of this project.
Foundry Project Architecture
Also we had already deployed a gpt-4o model. We will make use of this.
Foundry Project Architecture
Do remember, that azure foundry sdk there gets updated quite regularly. So do take reference of the documentation - Azure Python SDK Documentation

Configuration - Setting Up Environment Variables

We need to configure two important pieces of information: the Azure AI endpoint and the model deployment name. Create a .env file in your project directory:
MODEL_DEPLOYMENT_NAME="gpt-4o"
AZURE_AI_ENDPOINT="https://new-foundry-ai-project--resource.services.ai.azure.com/api/projects/new-foundry-ai-project-1"

Creating the Agent with Functions

Now let us create an agent that can use these functions. Create agent.py:
import os
import time
import json
from azure.ai.agents import AgentsClient
from azure.ai.agents.models import ListSortOrder, FunctionTool, ToolSet, RequiredAction
from azure.identity import DefaultAzureCredential
from dotenv import load_dotenv

# Import our airline functions
from airline_functions import available_functions, check_booking, modify_seat, process_refund, send_email

# Load environment variables
load_dotenv()

# Get configuration
endpoint = os.getenv("AZURE_AI_ENDPOINT")
model_deployment = os.getenv("MODEL_DEPLOYMENT_NAME")

# Validate configuration
if not all([endpoint, model_deployment]):
    raise ValueError("Missing required environment variables")

# Create AgentsClient
agents_client = AgentsClient(
    endpoint=endpoint,
    credential=DefaultAzureCredential(exclude_environment_credential=True)
)

print("Connected to Azure AI Foundry")

# Create FunctionTool with actual Python functions
functions = FunctionTool(functions=[check_booking, modify_seat, process_refund, send_email])

# Create agent with function calling capability
with agents_client:
    agent = agents_client.create_agent(
        model=model_deployment,
        name="airline-booking-agent",
        instructions="""You are a helpful airline customer service agent. 
        You can help customers with their flight bookings by:
        - Looking up booking details
        - Changing seat assignments
        - Processing refunds
        - Sending confirmation emails
        
        Always be polite and professional. When you successfully complete an action,
        confirm what you did for the customer.""",
        tools=functions.definitions
    )
    print(f"Created agent: {agent.id}")

    # Create thread
    thread = agents_client.threads.create()
    print(f"Created thread: {thread.id}")

    # Customer request
    user_message = "Hi, can you check my booking ABC123 and then change my seat to 12A?"
    
    message = agents_client.messages.create(
        thread_id=thread.id,
        role="user",
        content=user_message
    )
    print(f"\nUser: {user_message}")

    # Run agent
    run = agents_client.runs.create(
        thread_id=thread.id,
        agent_id=agent.id
    )

    # Poll and handle function calls
    while run.status in ["queued", "in_progress", "requires_action"]:
        time.sleep(1)
        run = agents_client.runs.get(thread_id=thread.id, run_id=run.id)
        
        print(f"Run status: {run.status}")
        
        # Handle function calling
        if run.status == "requires_action":
            print("\n[AGENT REQUESTING FUNCTION CALLS]")
            
            # Get the required actions
            tool_calls = run.required_action.submit_tool_outputs.tool_calls
            tool_outputs = []
            
            for tool_call in tool_calls:
                function_name = tool_call.function.name
                function_args = json.loads(tool_call.function.arguments)
                
                print(f"\nFunction: {function_name}")
                print(f"Arguments: {function_args}")
                
                # Execute the function
                if function_name in available_functions:
                    function_to_call = available_functions[function_name]
                    function_response = function_to_call(**function_args)
                    
                    print(f"Response: {function_response}")
                    
                    # Add the function output
                    tool_outputs.append({
                        "tool_call_id": tool_call.id,
                        "output": json.dumps(function_response)
                    })
                else:
                    print(f"Unknown function: {function_name}")
            
            # Submit all function outputs back to the agent
            if tool_outputs:
                print("\n[SUBMITTING FUNCTION OUTPUTS TO AGENT]")
                run = agents_client.runs.submit_tool_outputs(
                    thread_id=thread.id,
                    run_id=run.id,
                    tool_outputs=tool_outputs
                )

    print(f"\nRun completed with status: {run.status}")

    # Check for errors
    if run.status == "failed":
        print(f"Run failed: {run.last_error}")

    # Get messages
    messages = agents_client.messages.list(
        thread_id=thread.id,
        order=ListSortOrder.ASCENDING
    )

    # Display conversation
    print("\n" + "="*60)
    print("CONVERSATION")
    print("="*60)
    for msg in messages:
        if msg.text_messages:
            last_text = msg.text_messages[-1]
            print(f"\n{msg.role.upper()}: {last_text.text.value}")
    print("="*60)

Understanding the Function Calling Code

Let us break down the key parts of the agent code:

1. Create FunctionTool

functions = FunctionTool(function_definitions)
This creates a tool object from our function definitions. The FunctionTool class wraps our function definitions in the format Azure AI Agents expects.

2. Create Agent with Tools

agent = agents_client.create_agent(
    model=model_deployment,
    name="airline-booking-agent",
    instructions="...",
    tools=functions.definitions
)
We pass functions.definitions to the tools parameter, giving the agent access to all our airline functions.

3. Handle requires_action Status

if run.status == "requires_action":
    tool_calls = run.required_action.submit_tool_outputs.tool_calls
When the agent wants to call a function, the run status becomes "requires_action". We need to execute the requested functions and submit the outputs back.

4. Execute Functions

for tool_call in tool_calls:
    function_name = tool_call.function.name
    function_args = json.loads(tool_call.function.arguments)
    
    function_to_call = available_functions[function_name]
    function_response = function_to_call(**function_args)
    
    tool_outputs.append({
        "tool_call_id": tool_call.id,
        "output": json.dumps(function_response)
    })
For each function the agent wants to call:
  • Extract the function name and arguments
  • Look up the actual Python function from our available_functions dict
  • Execute the function with the provided arguments
  • Store the result to send back to the agent

5. Submit Function Outputs

run = agents_client.runs.submit_tool_outputs(
    thread_id=thread.id,
    run_id=run.id,
    tool_outputs=tool_outputs
)
Send all function results back to the agent so it can continue processing and formulate a response.

Running the Airline Agent

Now let us run the agent and see function calling in action!
python agent_functions.py
You should see output like:
Connected to Azure AI Foundry
Created agent: asst_abc123
Created thread: thread_xyz789

User: Hi, can you check my booking ABC123 and then change my seat to 12A?
Run status: in_progress

[AGENT REQUESTING FUNCTION CALLS]

Function: check_booking
Arguments: {'confirmation_number': 'ABC123'}
[FUNCTION CALLED] check_booking('ABC123')
Response: {'success': True, 'booking': {'booking_id': 'ABC123', ...}}

Function: modify_seat
Arguments: {'booking_id': 'ABC123', 'new_seat': '12A'}
[FUNCTION CALLED] modify_seat('ABC123', '12A')
Response: {'success': True, 'message': 'Seat changed from 14B to 12A', 'new_seat': '12A'}

[SUBMITTING FUNCTION OUTPUTS TO AGENT]
Run status: in_progress
Run completed with status: completed

============================================================
CONVERSATION
============================================================

USER: Hi, can you check my booking ABC123 and then change my seat to 12A?

ASSISTANT: I've checked your booking ABC123. You're booked on flight AA101 from 
New York (JFK) to London (LHR) on June 15th, 2026. Your original seat was 14B.

I've successfully changed your seat to 12A as requested. Is there anything else 
I can help you with?
============================================================
Look at what happened! The agent:
  1. Understood the user wanted to check a booking AND change a seat
  2. Called check_booking() first to get the booking details
  3. Called modify_seat() to change the seat to 12A
  4. Used the results to formulate a natural, helpful response
All of this happened autonomously - the agent decided what functions to call, in what order, and with what parameters!

Testing More Complex Scenarios

Let us test the agent with different types of requests:

Scenario 1: Refund Request

user_message = "I need to cancel my booking XYZ789 and get a refund of $450"

message = agents_client.messages.create(
    thread_id=thread.id,
    role="user",
    content=user_message
)
The agent will:
  1. Call process_refund("XYZ789", 450)
  2. Confirm the refund was processed
  3. Optionally call send_email() to send confirmation

Scenario 2: Just Information Lookup

user_message = "What's on my booking ABC123?"

message = agents_client.messages.create(
    thread_id=thread.id,
    role="user",
    content=user_message
)
The agent will:
  1. Call check_booking("ABC123")
  2. Present the booking details in a user-friendly way

Scenario 3: Invalid Booking

user_message = "Change my seat to 10C for booking INVALID999"

message = agents_client.messages.create(
    thread_id=thread.id,
    role="user",
    content=user_message
)
The agent will:
  1. Try to call modify_seat("INVALID999", "10C")
  2. Get an error response from the function
  3. Politely inform the user that the booking was not found

Function Calling Flow Diagram

Here is the complete flow when function calling is involved:
Function Calling Flow Diagram

1. User sends message
   
2. Agent analyzes message
   
3. Agent decides it needs to call function(s)
   
4. Run status becomes "requires_action"
   
5. Your code extracts function calls
   
6. Your code executes Python functions
   
7. Your code submits function outputs
   
8. Agent processes outputs
   
9. Agent formulates response
   
10. Run status becomes "completed"
    
11. User receives response

Multiple Function Calls in Sequence

One powerful aspect of function calling is that the agent can chain multiple functions together. For example:
User: "Check my booking ABC123, change my seat to 12A, and send me a confirmation email"
The agent will:
  1. Call check_booking("ABC123") to get booking details and email
  2. Call modify_seat("ABC123", "12A") to change the seat
  3. Call send_email(email, "Your seat has been changed to 12A")
  4. Respond to the user confirming all actions
All of this is orchestrated automatically by the agent!

Function Definition Best Practices

When defining functions for your agent, follow these best practices:

1. Clear Descriptions

"description": "Change the seat assignment for an existing booking"
The description should clearly explain what the function does. The LLM uses this to decide when to call it.

2. Detailed Parameter Descriptions

"new_seat": {
    "type": "string",
    "description": "The new seat number (e.g., 12A, 23B)"
}
Include examples in parameter descriptions to help the LLM understand the expected format.

3. Specify Required Parameters

"required": ["booking_id", "new_seat"]
Mark which parameters are mandatory so the LLM knows it must extract them.

4. Return Structured Data

return {
    "success": True,
    "message": "Seat changed successfully",
    "new_seat": "12A"
}
Always return a dictionary with a clear structure. Include a success field and descriptive messages.

5. Handle Errors Gracefully

if booking_id not in bookings_db:
    return {
        "success": False,
        "error": f"No booking found with ID {booking_id}"
    }
Return error information in the same format as success responses, so the agent can communicate errors to users.

Security Considerations

When implementing function calling, security is critical:

1. Validate All Inputs

def modify_seat(booking_id: str, new_seat: str) -> dict:
    # Validate seat format
    if not new_seat or len(new_seat) < 2:
        return {"success": False, "error": "Invalid seat format"}
    
    # Validate booking exists
    if booking_id not in bookings_db:
        return {"success": False, "error": "Booking not found"}
Never trust that the LLM will provide valid input. Always validate.

2. Implement Authorization

In a real system, you would verify the user has permission to modify the booking:
def modify_seat(booking_id: str, new_seat: str, user_email: str) -> dict:
    booking = bookings_db.get(booking_id)
    
    # Check if user owns this booking
    if booking["email"] != user_email:
        return {"success": False, "error": "Unauthorized"}

3. Limit Dangerous Operations

Some operations should require additional confirmation:
def process_refund(booking_id: str, amount: float, confirmed: bool = False) -> dict:
    if not confirmed:
        return {
            "success": False,
            "requires_confirmation": True,
            "message": f"Please confirm refund of ${amount}"
        }

4. Log All Function Calls

def modify_seat(booking_id: str, new_seat: str) -< dict:
    print(f"[AUDIT] Seat change: {booking_id} -< {new_seat}")
    # Log to database/file for auditing
Maintain an audit trail of all actions taken by the agent.

Comparison: Bing Search vs Function Calling

Aspect Bing Search Tool Function Calling
Purpose Retrieve public web information Execute custom business logic
Data Source Public internet (via Bing) Your databases, APIs, systems
Can Take Actions? No (read-only) Yes (can modify data, trigger workflows)
Setup Complexity Low (just add connection) Medium (write and define functions)
Customization Limited (searches Bing) Complete (you write the code)
Use Cases News, weather, stock prices, general info Bookings, orders, CRM, database operations
Cost Per Bing search API call Your infrastructure costs
You can (and often should) use both together! For example, an airline agent might use Bing to check current weather at destinations, while using function calling to manage bookings.

Real-World Integration Examples

Here are examples of how to integrate function calling with real systems:

Example 1: Database Integration

import psycopg2

def check_booking(confirmation_number: str) -> dict:
    conn = psycopg2.connect("dbname=airline user=postgres")
    cur = conn.cursor()
    
    cur.execute(
        "SELECT * FROM bookings WHERE confirmation_number = %s",
        (confirmation_number,)
    )
    
    result = cur.fetchone()
    conn.close()
    
    if result:
        return {"success": True, "booking": dict(result)}
    else:
        return {"success": False, "error": "Booking not found"}

Example 2: REST API Integration

import requests

def send_email(customer_email: str, message: str) -> dict:
    # Call SendGrid API
    response = requests.post(
        "https://api.sendgrid.com/v3/mail/send",
        headers={"Authorization": f"Bearer {SENDGRID_API_KEY}"},
        json={
            "personalizations": [{"to": [{"email": customer_email}]}],
            "from": {"email": "noreply@airline.com"},
            "subject": "Booking Update",
            "content": [{"type": "text/plain", "value": message}]
        }
    )
    
    if response.status_code == 202:
        return {"success": True, "message": "Email sent"}
    else:
        return {"success": False, "error": "Failed to send email"}

Example 3: Payment Processing

import stripe

def process_refund(booking_id: str, amount: float) -> dict:
    booking = get_booking_from_db(booking_id)
    
    try:
        refund = stripe.Refund.create(
            payment_intent=booking["payment_intent_id"],
            amount=int(amount * 100)  # Stripe uses cents
        )
        
        update_booking_status(booking_id, "refunded")
        
        return {
            "success": True,
            "message": f"Refund of ${amount} processed",
            "refund_id": refund.id
        }
    except stripe.error.StripeError as e:
        return {"success": False, "error": str(e)}

Error Handling in Function Calling

Proper error handling is crucial. Here is a robust pattern:
while run.status in ["queued", "in_progress", "requires_action"]:
    time.sleep(1)
    run = agents_client.runs.get(thread_id=thread.id, run_id=run.id)
    
    if run.status == "requires_action":
        tool_calls = run.required_action.submit_tool_outputs.tool_calls
        tool_outputs = []
        
        for tool_call in tool_calls:
            try:
                function_name = tool_call.function.name
                function_args = json.loads(tool_call.function.arguments)
                
                if function_name not in available_functions:
                    raise ValueError(f"Unknown function: {function_name}")
                
                function_to_call = available_functions[function_name]
                function_response = function_to_call(**function_args)
                
                tool_outputs.append({
                    "tool_call_id": tool_call.id,
                    "output": json.dumps(function_response)
                })
                
            except Exception as e:
                # Return error to agent
                error_response = {
                    "success": False,
                    "error": f"Function execution failed: {str(e)}"
                }
                tool_outputs.append({
                    "tool_call_id": tool_call.id,
                    "output": json.dumps(error_response)
                })
        
        # Submit outputs (including errors)
        run = agents_client.runs.submit_tool_outputs(
            thread_id=thread.id,
            run_id=run.id,
            tool_outputs=tool_outputs
        )
This ensures that even if a function fails, the agent can handle it gracefully and inform the user.

Limitations and Considerations

1. Function Execution Timeout

Functions should execute quickly (under 30 seconds). Long-running operations should be handled asynchronously:
def start_refund_process(booking_id: str) -> dict:
    # Start async task
    task_id = queue_refund_task(booking_id)
    
    return {
        "success": True,
        "message": "Refund processing started",
        "task_id": task_id,
        "status": "pending"
    }

2. Cost Implications

Each function call adds to the token count and processing time. Design functions to be efficient.

3. Context Window Limits

Large function outputs consume tokens. Return only essential data:
# Bad - returns too much data
return {"success": True, "all_bookings": list_of_1000_bookings}

# Good - returns summary
return {"success": True, "total_bookings": 1000, "active_bookings": 50}

4. Agent Reliability

The LLM might not always call the right function or extract parameters correctly. Validate everything and provide clear feedback.

What We Have Learned

In this tutorial, we learned:
  • What function calling is and why it is powerful
  • How to define Python functions for agents to call
  • How to create function definitions in the correct format
  • How to handle the requires_action status
  • How to execute functions and submit outputs back to the agent
  • How agents orchestrate multiple function calls
  • Best practices for security and error handling
  • How to integrate with real databases and APIs
Function calling transforms agents from information providers into action-takers that can integrate with your business systems!

Next Steps

In upcoming tutorials, we will explore:
  • RAG (Retrieval Augmented Generation) - Give agents access to your document knowledge bases
  • Code Interpreter - Let agents write and execute Python code for data analysis
  • File Upload and Processing - Work with user-uploaded documents
  • MCP (Model Context Protocol) - Connect to external systems
  • Multi-Agent Workflows - Coordinate specialized agents for complex tasks

Complete Code

airline_functions.py - [See the complete function definitions above] agent_functions.py - [See the complete agent code above] .env:
MODEL_DEPLOYMENT_NAME="gpt-4o"
AZURE_AI_ENDPOINT="https://new-foundry-ai-project--resource.services.ai.azure.com/api/projects/new-foundry-ai-project-1"

Download Source Code

Download it -
Azure AI Agent Function Calling

Azure AI Agents - Table of Contents

Jump between Foundry tutorials, SDK builds, and grounded agent examples.

Popular Posts