Getting Multiple Function Calls to Work Reliably

I’m having trouble getting multiple function calls to work reliably.

Problem Description

When I ask the model to perform multiple tasks that should each trigger a separate function call, it only calls one function instead of both. For example, with this user message:

What’s the weather like in London today and can you send that information to [email protected]?

I expect the model to:

  1. Call get_weather to get the weather in London
  2. Call send_email to send that information to the email address

However, the model only calls the get_weather function and then returns None as the final response, instead of incorporating the weather information into an email or explicitly calling the email function.

What I’ve Tried

  • Setting tool_choice="required" to force function calls
  • Setting parallel_tool_calls=True to explicitly enable multiple function calls
  • Using different model versions
  • Adding detailed function descriptions
  • Rewriting the user query to be more explicit

Nothing seems to work consistently. The model calls just one function, this is true for gpt-4o and o3-mini, both models which i’d expect to be able to handle this.

Minimal Reproducible Example

I’ve attached a simple script that demonstrates the issue. It’s a minimal version that defines two functions (weather and email) and tries to get the model to use both in sequence.

Questions

  1. Is there something wrong with my implementation of function calling?
  2. Should I be using a different approach entirely for multi-step tasks like this?

Any help would be greatly appreciated!

Code to reproduce:

from openai import OpenAI
import json
import time


# Initialize OpenAI client
client = OpenAI()

# Define the functions
def get_weather(location):
    """Mock weather function"""
    return f"Current weather in {location}: 15°C, partly cloudy"

def send_email(to, subject, body):
    """Mock email function"""
    print(f"\nWould send email to: {to}")
    print(f"Subject: {subject}")
    print(f"Body: {body}\n")
    return f"Email sent to {to} successfully!"

def call_function(name, args):
    """Router function to call the appropriate function"""
    if name == "get_weather":
        return get_weather(args.get("location", ""))
    elif name == "send_email":
        return send_email(args.get("to", ""), args.get("subject", ""), args.get("body", ""))
    else:
        return f"Function {name} not implemented"

# Define tools for the model
tools = [
    {
        "type": "function",
        "function": {
            "name": "get_weather",
            "description": "Get current weather for a location",
            "parameters": {
                "type": "object",
                "properties": {
                    "location": {
                        "type": "string",
                        "description": "City name, e.g., London, UK"
                    }
                },
                "required": ["location"],
                "additionalProperties": False
            },
            "strict": True
        }
    },
    {
        "type": "function",
        "function": {
            "name": "send_email",
            "description": "Send an email to a recipient",
            "parameters": {
                "type": "object",
                "properties": {
                    "to": {
                        "type": "string",
                        "description": "Recipient email address"
                    },
                    "subject": {
                        "type": "string",
                        "description": "Email subject line"
                    },
                    "body": {
                        "type": "string",
                        "description": "Email body content"
                    }
                },
                "required": ["to", "subject", "body"],
                "additionalProperties": False
            },
            "strict": True
        }
    }
]

# Main test function
def test_multiple_functions():
    print("Testing multiple function calls...")
    user_message = "What's the weather like in London today and can you send that information to [email protected]?"
    
    print(f"User message: '{user_message}'")
    messages = [{"role": "user", "content": user_message}]
    
    # First completion to trigger function calls
    print("\n=== First API Call ===")
    completion = client.chat.completions.create(
        model="gpt-4o",
        messages=messages,
        tools=tools,
        # Force tool calls 
        tool_choice="required",
        parallel_tool_calls=True
    )
    
    assistant_message = completion.choices[0].message
    tool_calls = assistant_message.tool_calls
    
    # Add the assistant message to conversation
    messages.append(assistant_message)

    print(f"messages after first completion: {messages}")
    
    if not tool_calls:
        print("No function calls were made by the model.")
        return
    
    print(f"Number of function calls: {len(tool_calls)}")
    
    # Process all tool calls
    for i, tool_call in enumerate(tool_calls):
        function_name = tool_call.function.name
        function_args = json.loads(tool_call.function.arguments)
        
        print(f"\nFunction call {i+1}: {function_name}")
        print(f"Arguments: {function_args}")
        
        # Execute the function
        function_response = call_function(function_name, function_args)
        print(f"Function result: {function_response}")
        
        # Add result to messages
        messages.append({
            "role": "tool",
            "tool_call_id": tool_call.id,
            "content": function_response
        })
    
    print(f"messages after tool calls: {messages}")
    # Second completion to get final response
    print("\n=== Second API Call ===")
    second_completion = client.chat.completions.create(
        model="gpt-4o",
        messages=messages,
        tools=tools,
        tool_choice="auto"  # Dont need tool calls for final response
    )
    
    final_response = second_completion.choices[0].message.content
    print(f"\nFinal response: {final_response}")

if __name__ == "__main__":
    test_multiple_functions()

I’m missing something fundamental so any help/pointers would be greatly appreciated!

1 Like